%% %% %CopyrightBegin% %% %% Copyright Ericsson AB 2020-2023. All 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, monitor/1]}). %% Administrative and "global" utility functions -export([ %% (registry) Socket functions number_of/0, which_sockets/0, which_sockets/1, %% (registry) Socket monitor functions number_of_monitors/0, number_of_monitors/1, which_monitors/1, monitored_by/1, debug/1, socket_debug/1, use_registry/1, info/0, info/1, i/0, i/1, i/2, tables/0, table/1, monitor/1, cancel_monitor/1, supports/0, supports/1, supports/2, is_supported/1, is_supported/2, is_supported/3, to_list/1 ]). -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, sendfile/2, sendfile/3, sendfile/4, sendfile/5, recv/1, recv/2, recv/3, recv/4, recvfrom/1, recvfrom/2, recvfrom/3, recvfrom/4, recvmsg/1, recvmsg/2, recvmsg/3, recvmsg/4, recvmsg/5, close/1, shutdown/2, setopt/3, setopt_native/3, setopt/4, getopt/2, getopt_native/3, getopt/3, sockname/1, peername/1, ioctl/2, ioctl/3, ioctl/4, cancel/2 ]). %% Misc utility functions -export([ which_socket_kind/1 ]). -export_type([ socket/0, socket_handle/0, select_tag/0, select_handle/0, select_info/0, completion_tag/0, completion_handle/0, completion_info/0, invalid/0, socket_counters/0, socket_info/0, domain/0, type/0, protocol/0, port_number/0, in_addr/0, in6_addr/0, sockaddr/0, sockaddr_recv/0, sockaddr_in/0, sockaddr_in6/0, sockaddr_un/0, sockaddr_ll/0, sockaddr_dl/0, sockaddr_unspec/0, sockaddr_native/0, msg_flag/0, level/0, otp_socket_option/0, socket_option/0, %% Option values' types linger/0, timeval/0, ip_mreq/0, ip_mreq_source/0, ip_msfilter/0, ip_pmtudisc/0, ip_tos/0, ip_pktinfo/0, ipv6_mreq/0, ipv6_pmtudisc/0, ipv6_hops/0, ipv6_pktinfo/0, sctp_assocparams/0, sctp_event_subscribe/0, sctp_initmsg/0, sctp_rtoinfo/0, msg/0, msg_send/0, msg_recv/0, cmsg/0, cmsg_send/0, cmsg_recv/0, ee_origin/0, icmp_dest_unreach/0, icmpv6_dest_unreach/0, extended_err/0, info_keys/0 ]). %% DUMMY -export_type([ioctl_device_flag/0, ioctl_device_map/0]). %% We need #file_descriptor{} for sendfile/2,3,4,5 -include("file_int.hrl"). %% -define(DBG(T), erlang:display({{self(), ?MODULE, ?LINE, ?FUNCTION_NAME}, T})). %% Also in prim_socket -define(REGISTRY, socket_registry). -type invalid() :: {invalid, What :: term()}. -type info() :: #{counters := #{atom() := non_neg_integer()}, iov_max := non_neg_integer(), use_registry := boolean(), io_backend := #{name := atom()}}. -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(), sendfile => non_neg_integer(), sendfile_byte => non_neg_integer(), sendfile_fails => non_neg_integer(), sendfile_max => non_neg_integer(), sendfile_pkg => non_neg_integer(), sendfile_pkg_max => non_neg_integer(), sendfile_tries => non_neg_integer(), sendfile_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() | integer(), type := type() | integer(), protocol := protocol() | integer(), owner := 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(), rstates := [atom()], wstates := [atom()]}. %% We support only a subset of all domains. -type domain() :: inet | inet6 | local | unspec. %% We support only a subset of all types. %% RDM - Reliably Delivered Messages -type type() :: stream | dgram | raw | rdm | seqpacket. %% We support all protocols enumerated by getprotoent(), %% and all of ip | ipv6 | tcp | udp | sctp that are supported %% by the platform, even if not enumerated by getprotoent() -type protocol() :: atom(). -type port_number() :: 0..65535. -type in_addr() :: {0..255, 0..255, 0..255, 0..255}. -type in6_flow_info() :: 0..16#FFFFF. -type in6_scope_id() :: 0..16#FFFFFFFF. -type in6_addr() :: {0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535, 0..65535}. -type linger() :: #{onoff := boolean(), linger := non_neg_integer()}. -type timeval() :: #{sec := integer(), usec := integer()}. -type ip_mreq() :: #{multiaddr := in_addr(), interface := in_addr()}. -type ip_mreq_source() :: #{multiaddr := in_addr(), interface := in_addr(), sourceaddr := in_addr()}. -type ip_msfilter() :: #{multiaddr := in_addr(), interface := in_addr(), mode := 'include' | 'exclude', slist := [ in_addr() ]}. -type ip_pmtudisc() :: want | dont | do | probe. %% If the integer value is used, its up to the caller to ensure its valid! -type ip_tos() :: lowdelay | throughput | reliability | mincost. -type ip_pktinfo() :: #{ifindex := non_neg_integer(), % Interface Index spec_dst := in_addr(), % Local Address addr := in_addr() % Header Destination address }. -type ipv6_mreq() :: #{multiaddr := in6_addr(), interface := non_neg_integer()}. -type ipv6_pmtudisc() :: want | dont | do | probe. -type ipv6_hops() :: default | 0..255. -type ipv6_pktinfo() :: #{addr := in6_addr(), ifindex := integer() }. -type sctp_assocparams() :: #{assoc_id := integer(), asocmaxrxt := 0..16#ffff, numbe_peer_destinations := 0..16#ffff, peer_rwnd := 0..16#ffffffff, local_rwnd := 0..16#ffffffff, cookie_life := 0..16#ffffffff}. -type sctp_event_subscribe() :: #{data_io := boolean(), association := boolean(), address := boolean(), send_failure := boolean(), peer_error := boolean(), shutdown := boolean(), partial_delivery := boolean(), adaptation_layer => boolean(), sender_dry => boolean()}. -type sctp_initmsg() :: #{num_ostreams := 0..16#ffff, max_instreams := 0..16#ffff, max_attempts := 0..16#ffff, max_init_timeo := 0..16#ffff}. -type sctp_rtoinfo() :: #{assoc_id := integer(), initial := 0..16#ffffffff, max := 0..16#ffffffff, min := 0..16#ffffffff}. -type packet_type() :: host | broadcast | multicast | otherhost | outgoing | loopback | user | kernel | fastroute | non_neg_integer(). -type hatype() :: netrom | eether | ether | ax25 | pronet | chaos | ieee802 | arcnet | appletlk | dlci | atm | metricom | ieee1394 | eui64 | infiniband | tunnel | tunnel6 | loopback | localtlk | none | void | non_neg_integer(). -type sockaddr_un() :: #{family := 'local', path := binary() | string()}. -type sockaddr_in() :: #{family := 'inet', port := port_number(), %% The 'broadcast' here is the "limited broadcast" addr := 'any' | 'broadcast' | 'loopback' | in_addr()}. -type sockaddr_in6() :: #{family := 'inet6', port := port_number(), addr := 'any' | 'loopback' | in6_addr(), flowinfo := in6_flow_info(), scope_id := in6_scope_id()}. -type sockaddr_ll() :: #{family := 'packet', protocol := non_neg_integer(), ifindex := integer(), pkttype := packet_type(), hatype := hatype(), addr := binary()}. -type sockaddr_dl() :: #{family := 'link', index := non_neg_integer(), type := non_neg_integer(), nlen := non_neg_integer(), alen := non_neg_integer(), slen := non_neg_integer(), data := binary()}. -type sockaddr_unspec() :: #{family := 'unspec', addr := binary()}. -type sockaddr_native() :: #{family := integer(), addr := binary()}. -type sockaddr() :: sockaddr_in() | sockaddr_in6() | sockaddr_un() | sockaddr_ll() | sockaddr_dl() | sockaddr_unspec() | sockaddr_native(). -type sockaddr_recv() :: sockaddr() | binary(). %% (otp) - This option is internal to our (OTP) implementation. %% socket - The socket layer (SOL_SOCKET). %% (Int) - Raw level, sent down and used "as is". %% protocol() - Protocol number; ip | ipv6 | tcp | udp | sctp | ... -type level() :: %% otp | % Has got own clauses in setopt/getopt %% integer() % Has also got own clauses socket | %% Handled explicitly protocol(). %% 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 {error, invalid()}? %% Do we really need a sndbuf? -type otp_socket_option() :: debug | iow | controlling_process | rcvbuf | % sndbuf | rcvctrlbuf | sndctrlbuf | meta | use_registry | fd | domain. -type socket_option() :: {Level :: socket, Opt :: 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} | {Level :: ip, Opt :: 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 | recvdstaddr | recverr | recvif | recvopts | recvorigdstaddr | recvtos | recvttl | retopts | router_alert | sndsrcaddr | tos | transparent | ttl | unblock_source} | {Level :: ipv6, Opt :: 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} | {Level :: tcp, Opt :: congestion | cork | info | keepcnt | keepidle | keepintvl | maxseg | md5sig | nodelay | noopt | nopush | syncnt | user_timeout} | {Level :: udp, Opt :: cork} | {Level :: sctp, Opt :: 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}. %% 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(socket(Ref), {?socket_tag, (Ref)}). %% %% Messages sent from the nif-code to erlang processes: -define(socket_msg(Socket, Tag, Info), {?socket_tag, (Socket), (Tag), (Info)}). -type socket() :: ?socket(socket_handle()). -opaque socket_handle() :: reference(). %% Some flags are used for send, others for recv, and yet again %% others are found in a cmsg(). They may occur in multiple locations.. -type msg_flag() :: cmsg_cloexec | confirm | ctrunc | dontroute | eor | errqueue | more | oob | peek | trunc. -type msg() :: msg_send() | msg_recv(). -type msg_send() :: #{ %% *Optional* target address %% Used on an unconnected socket to specify the %% destination address for a message. addr => sockaddr(), iov := erlang:iovec(), %% *Optional* control message list (ancillary data). %% The maximum size of the control buffer is platform %% specific. It is the users responsibility to ensure %% that its not exceeded. %% ctrl => ([cmsg_send() | #{level := level() | integer(), type := integer(), data := binary()}]) }. -type msg_recv() :: #{ %% *Optional* target address %% Used on an unconnected socket to return the %% source address for a message. addr => sockaddr_recv(), iov := erlang:iovec(), %% Control messages (ancillary data). %% The maximum size of the control buffer is platform %% specific. It is the users responsibility to ensure %% that its not exceeded. %% ctrl := ([cmsg_recv() | #{level := level() | integer(), type := integer(), data := binary()}]), %% Received message flags flags := [msg_flag() | integer()] }. %% 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. The data is always delivered as a binary() %% and a decoded value is delivered in the 'value' field, if decoding %% is successful. -type cmsg() :: cmsg_recv() | cmsg_send(). -type cmsg_recv() :: #{level := socket, type := timestamp, data := binary(), value => timeval()} | #{level := socket, type := rights, data := binary()} | #{level := socket, type := credentials, data := binary()} | #{level := ip, type := tos, data := binary(), value => ip_tos() | integer()} | #{level := ip, type := recvtos, data := binary(), value := ip_tos() | integer()} | #{level := ip, type := ttl, data := binary(), value => integer()} | #{level := ip, type := recvttl, data := binary(), value := integer()} | #{level := ip, type := pktinfo, data := binary(), value => ip_pktinfo()} | #{level := ip, type := origdstaddr, data := binary(), value => sockaddr_recv()} | #{level := ip, type := recverr, data := binary(), value => extended_err()} | #{level := ipv6, type := hoplimit, data := binary(), value => integer()} | #{level := ipv6, type := pktinfo, data := binary(), value => ipv6_pktinfo()} | #{level := ipv6, type := recverr, data := binary(), value => extended_err()} | #{level := ipv6, type := tclass, data := binary(), value => integer()}. -type native_value() :: integer() | boolean() | binary(). %% Possible to add type tagged values a'la {uint16, 0..16#FFFF} -type cmsg_send() :: #{level := socket, type := timestamp, data => native_value(), value => timeval()} | #{level := socket, type := rights, data := native_value()} | #{level := socket, type := credentials, data := native_value()} | #{level := ip, type := tos, data => native_value(), value => ip_tos() | integer()} | #{level := ip, type := ttl, data => native_value(), value => integer()} | #{level := ip, type := hoplimit, data => native_value(), value => integer()} | #{level := ipv6, type := tclass, data => native_value(), value => integer()}. -type ee_origin() :: none | local | icmp | icmp6. -type icmp_dest_unreach() :: net_unreach | host_unreach | port_unreach | frag_needed | net_unknown | host_unknown. -type icmpv6_dest_unreach() :: noroute | adm_prohibited | not_neighbour | addr_unreach | port_unreach | policy_fail | reject_route. -type extended_err() :: #{error := posix(), origin := icmp, type := dest_unreach, code := icmp_dest_unreach() | 0..16#FF, info := 0..16#FFFFFFFF, data := 0..16#FFFFFFFF, offender := sockaddr_recv()} | #{error := posix(), origin := icmp, type := time_exceeded | 0..16#FF, code := 0..16#FF, info := 0..16#FFFFFFFF, data := 0..16#FFFFFFFF, offender := sockaddr_recv()} | #{error := posix(), origin := icmp6, type := dest_unreach, code := icmpv6_dest_unreach() | 0..16#FF, info := 0..16#FFFFFFFF, data := 0..16#FFFFFFFF, offender := sockaddr_recv()} | #{error := posix(), origin := icmp6, type := pkt_toobig | time_exceeded | 0..16#FF, code := 0..16#FF, info := 0..16#FFFFFFFF, data := 0..16#FFFFFFFF, offender := sockaddr_recv()} | #{error := posix(), origin := ee_origin() | 0..16#FF, type := 0..16#FF, code := 0..16#FF, info := 0..16#FFFFFFFF, data := 0..16#FFFFFFFF, offender := sockaddr_recv()}. -type posix() :: inet:posix(). -type info_keys() :: [ 'domain' | 'type' | 'protocol' | 'fd' | 'owner' | 'local_address' | 'remote_address' | 'recv' | 'sent' | 'state' ]. %% Note that not all flags exist on all platforms! -type ioctl_device_flag() :: up | broadcast | debug | loopback | pointopoint | notrailers | knowsepoch | running | noarp | promisc | allmulti | master | oactive | slave | simplex | link0 | link1 | link2 | multicast | portsel | automedia | cantconfig | ppromisc | dynamic | monitor | staticarp | dying | renaming | nogroup | lower_up | dormant | echo. %% When reading the device map (gifmap), the resulting map will be %% "fully" populated. %% %% When writing, it is expected that only the fields that is %% to be set is present. %% -type ioctl_device_map() :: #{mem_start := non_neg_integer(), mem_end := non_neg_integer(), base_addr := non_neg_integer(), irq := non_neg_integer(), dma := non_neg_integer(), port := non_neg_integer()}. %% =========================================================================== %% %% Interface term formats %% -opaque select_tag() :: atom() | {atom(), ContData :: term()}. -opaque completion_tag() :: atom(). % | {atom(), ContData :: term()}. -type select_handle() :: reference(). -type completion_handle() :: reference(). -type select_info() :: {select_info, SelectTag :: select_tag(), SelectHandle :: select_handle()}. -type completion_info() :: {completion_info, CompletionTag :: completion_tag(), CompletionHandle :: completion_handle()}. -define(SELECT_INFO(Tag, SelectHandle), {select_info, Tag, SelectHandle}). -define(COMPLETION_INFO(Tag, CompletionHandle), {completion_info, Tag, CompletionHandle}). %% =========================================================================== %% %% 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, according to the filter rule. %% -spec which_sockets() -> [socket()]. which_sockets() -> ?REGISTRY:which_sockets(true). -spec which_sockets(FilterRule) -> [socket()] when FilterRule :: 'inet' | 'inet6' | 'local' | 'stream' | 'dgram' | 'seqpacket' | 'sctp' | 'tcp' | 'udp' | pid() | fun((socket_info()) -> boolean()). which_sockets(Domain) when Domain =:= inet; Domain =:= inet6; Domain =:= local -> ?REGISTRY:which_sockets({domain, Domain}); which_sockets(Type) when Type =:= stream; Type =:= dgram; Type =:= seqpacket -> ?REGISTRY:which_sockets({type, Type}); which_sockets(Proto) when Proto =:= sctp; Proto =:= tcp; Proto =:= udp -> ?REGISTRY:which_sockets({protocol, Proto}); which_sockets(Owner) when is_pid(Owner) -> ?REGISTRY:which_sockets({owner, Owner}); which_sockets(Filter) when is_function(Filter, 1) -> ?REGISTRY:which_sockets(Filter); which_sockets(Other) -> erlang:error(badarg, [Other]). %% *** number_of_monitors *** %% %% Interface function to the socket registry %% returns the number of existing socket monitors. %% -spec number_of_monitors() -> non_neg_integer(). number_of_monitors() -> ?REGISTRY:number_of_monitors(). -spec number_of_monitors(pid()) -> non_neg_integer(). number_of_monitors(Pid) when is_pid(Pid) -> ?REGISTRY:number_of_monitors(Pid). %% *** which_monitors/1 *** %% %% Interface function to the socket registry %% Returns a list of all the monitors of the process or socket. %% -spec which_monitors(Pid) -> [reference()] when Pid :: pid(); (Socket) -> [reference()] when Socket :: socket(). which_monitors(Pid) when is_pid(Pid) -> ?REGISTRY:which_monitors(Pid); which_monitors(?socket(SockRef) = Socket) when is_reference(SockRef) -> ?REGISTRY:which_monitors(Socket); which_monitors(Socket) -> erlang:error(badarg, [Socket]). %% *** monitor_by/1 *** %% %% Interface function to the socket registry %% Returns a list of all the process'es monitoring the socket. %% -spec monitored_by(Socket) -> [reference()] when Socket :: socket(). monitored_by(?socket(SockRef) = Socket) when is_reference(SockRef) -> ?REGISTRY:monitored_by(Socket); monitored_by(Socket) -> erlang:error(badarg, [Socket]). %% *** to_list/1 *** %% %% This is intended to convert a socket() to a printable string. %% -spec to_list(Socket) -> list() when Socket :: socket(). to_list(?socket(SockRef)) when is_reference(SockRef) -> "#Ref" ++ Id = erlang:ref_to_list(SockRef), "#Socket" ++ Id; to_list(Socket) -> erlang:error(badarg, [Socket]). %% *** which_socket_kind/1 *** %% %% Utility function that returns the "kind" of socket. %% That is, if its a "plain" socket or a compatibillity socket. %% -spec which_socket_kind(Socket :: socket()) -> plain | compat. which_socket_kind(?socket(SockRef) = Socket) when is_reference(SockRef) -> case prim_socket:getopt(SockRef, {otp,meta}) of {ok, undefined} -> plain; {ok, _} -> compat; {error, _} -> erlang:error(badarg, [Socket]) end; which_socket_kind(Socket) -> erlang:error(badarg, [Socket]). %% =========================================================================== %% %% Debug features %% %% =========================================================================== -spec debug(D :: boolean()) -> 'ok'. %% debug(D) when is_boolean(D) -> prim_socket:debug(D); debug(D) -> erlang:error(badarg, [D]). -spec socket_debug(D :: boolean()) -> 'ok'. %% socket_debug(D) when is_boolean(D) -> prim_socket:socket_debug(D); socket_debug(D) -> erlang:error(badarg, [D]). -spec use_registry(D :: boolean()) -> 'ok'. %% use_registry(D) when is_boolean(D) -> prim_socket:use_registry(D). tables() -> #{protocols => table(protocols), options => table(options), ioctl_requests => table(ioctl_requests), ioctl_flags => table(ioctl_flags), msg_flags => table(msg_flags)}. table(Table) -> prim_socket:p_get(Table). %% =========================================================================== %% %% i/0,1,2 - List sockets %% %% This produces a list of "all" the sockets, and some info about each one. %% This function is intended as a utility and debug function. %% The sockets can be selected from domain, type or protocol. %% The sockets are not sorted. %% %% =========================================================================== -spec default_info_keys() -> info_keys(). default_info_keys() -> [ domain, type, protocol, fd, owner, local_address, remote_address, recv, sent, state ]. -spec i() -> ok. i() -> do_i(which_sockets(), default_info_keys()). -spec i(InfoKeys) -> ok when InfoKeys :: info_keys(); (Domain) -> ok when Domain :: inet | inet6 | local; (Proto) -> ok when Proto :: sctp | tcp | udp; (Type) -> ok when Type :: dgram | seqpacket | stream. i(InfoKeys) when is_list(InfoKeys) -> do_i(which_sockets(), InfoKeys); i(Domain) when (Domain =:= inet) orelse (Domain =:= inet6) orelse (Domain =:= local) -> do_i(which_sockets(Domain), default_info_keys()); i(Proto) when (Proto =:= tcp) orelse (Proto =:= udp) orelse (Proto =:= sctp) -> do_i(which_sockets(Proto), default_info_keys()); i(Type) when (Type =:= dgram) orelse (Type =:= seqpacket) orelse (Type =:= stream) -> do_i(which_sockets(Type), default_info_keys()). -spec i(Domain, InfoKeys) -> ok when Domain :: inet | inet6 | local, InfoKeys :: info_keys(); (Proto, InfoKeys) -> ok when Proto :: sctp | tcp | udp, InfoKeys :: info_keys(); (Type, InfoKeys) -> ok when Type :: dgram | seqpacket | stream, InfoKeys :: info_keys(). i(Domain, InfoKeys) when ((Domain =:= inet) orelse (Domain =:= inet6) orelse (Domain =:= local)) andalso is_list(InfoKeys) -> do_i(which_sockets(Domain), InfoKeys); i(Proto, InfoKeys) when ((Proto =:= tcp) orelse (Proto =:= udp) orelse (Proto =:= sctp)) andalso is_list(InfoKeys) -> do_i(which_sockets(Proto), InfoKeys); i(Type, InfoKeys) when ((Type =:= dgram) orelse (Type =:= seqpacket) orelse (Type =:= stream)) andalso is_list(InfoKeys) -> do_i(which_sockets(Type), InfoKeys). do_i(Sockets, InfoKeys) -> Lines = case i_sockets(Sockets, InfoKeys) of [] -> []; InfoLines -> [header_line(InfoKeys) | InfoLines] end, Maxs = lists:foldl(fun(Line, Max0) -> smax(Max0, Line) end, lists:duplicate(length(InfoKeys), 0), Lines), Fmt = lists:append(["~-" ++ integer_to_list(N) ++ "s " || N <- Maxs]) ++ "~n", lists:foreach(fun(Line) -> io:format(Fmt, Line) end, Lines). header_line(Fields) -> [header_field(atom_to_list(F)) || F <- Fields]. header_field([C | Cs]) -> [string:to_upper(C) | header_field_rest(Cs)]. header_field_rest([$_, C | Cs]) -> [$\s, string:to_upper(C) | header_field_rest(Cs)]; header_field_rest([C|Cs]) -> [C | header_field_rest(Cs)]; header_field_rest([]) -> []. smax([Max|Ms], [Str|Strs]) -> N = length(Str), [if N > Max -> N; true -> Max end | smax(Ms, Strs)]; smax([], []) -> []. i_sockets(Sockets, InfoKeys) -> [i_socket(Socket, InfoKeys) || Socket <- Sockets]. i_socket(Socket, InfoKeys) -> %% Most of the stuff we need, is in 'socket info' %% so we can just as well get it now. Info = #{protocol := Proto} = info(Socket), i_socket(Proto, Socket, Info, InfoKeys). i_socket(Proto, Socket, Info, InfoKeys) -> [i_socket_info(Proto, Socket, Info, InfoKey) || InfoKey <- InfoKeys]. i_socket_info(_Proto, _Socket, #{domain := Domain} = _Info, domain) -> atom_to_list(Domain); i_socket_info(_Proto, _Socket, #{type := Type} = _Info, type) -> string:to_upper(atom_to_list(Type)); i_socket_info(Proto, _Socket, #{type := Type} = _Info, protocol) -> string:to_upper(atom_to_list(if (Proto =:= 0) -> case Type of stream -> tcp; dgram -> udp; _ -> unknown end; true -> Proto end)); i_socket_info(_Proto, Socket, _Info, fd) -> try socket:getopt(Socket, otp, fd) of {ok, FD} -> integer_to_list(FD); {error, _} -> " " catch _:_ -> " " end; i_socket_info(_Proto, _Socket, #{owner := Pid} = _Info, owner) -> pid_to_list(Pid); i_socket_info(Proto, Socket, _Info, local_address) -> case sockname(Socket) of {ok, Addr} -> fmt_sockaddr(Addr, Proto); {error, _} -> " " end; i_socket_info(Proto, Socket, _Info, remote_address) -> try peername(Socket) of {ok, Addr} -> fmt_sockaddr(Addr, Proto); {error, _} -> " " catch _:_ -> " " end; i_socket_info(_Proto, _Socket, #{counters := #{read_byte := N}} = _Info, recv) -> integer_to_list(N); i_socket_info(_Proto, _Socket, #{counters := #{write_byte := N}} = _Info, sent) -> integer_to_list(N); i_socket_info(_Proto, _Socket, #{rstates := RStates, wstates := WStates} = _Info, state) -> fmt_states(RStates, WStates); i_socket_info(_Proto, _Socket, _Info, _Key) -> " ". fmt_states([], []) -> " "; fmt_states(RStates, []) -> fmt_states(RStates) ++ ", -"; fmt_states([], WStates) -> " - , " ++ fmt_states(WStates); fmt_states(RStates, WStates) -> fmt_states(RStates) ++ " , " ++ fmt_states(WStates). fmt_states([H]) -> fmt_state(H); fmt_states([H|T]) -> fmt_state(H) ++ ":" ++ fmt_states(T). fmt_state(accepting) -> "A"; fmt_state(bound) -> "BD"; fmt_state(busy) -> "BY"; fmt_state(connected) -> "CD"; fmt_state(connecting) -> "CG"; fmt_state(listen) -> "LN"; fmt_state(listening) -> "LG"; fmt_state(open) -> "O"; fmt_state(selected) -> "SD"; fmt_state(X) when is_atom(X) -> string:uppercase(atom_to_list(X)). fmt_sockaddr(#{family := Fam, addr := Addr, port := Port}, Proto) when (Fam =:= inet) orelse (Fam =:= inet6) -> case Addr of {0,0,0,0} -> "*:" ++ fmt_port(Port, Proto); {0,0,0,0,0,0,0,0} -> "*:" ++ fmt_port(Port, Proto); {127,0,0,1} -> "localhost:" ++ fmt_port(Port, Proto); {0,0,0,0,0,0,0,1} -> "localhost:" ++ fmt_port(Port, Proto); IP -> inet_parse:ntoa(IP) ++ ":" ++ fmt_port(Port, Proto) end; fmt_sockaddr(#{family := local, path := Path}, _Proto) -> "local:" ++ if is_list(Path) -> Path; is_binary(Path) -> binary_to_list(Path) end. fmt_port(N, Proto) -> case inet:getservbyport(N, Proto) of {ok, Name} -> f("~s (~w)", [Name, N]); _ -> integer_to_list(N) end. %% =========================================================================== %% %% info - Get miscellaneous information about a socket %% or about the socket library. %% %% Generates a list of various info about the socket, such as counter values. %% %% Do *not* call this function often. %% %% =========================================================================== -spec info() -> info(). %% info() -> try prim_socket:info() catch error:undef:ST -> case ST of %% We rewrite errors coming from prim_socket not existing %% to enotsup. [{prim_socket,info,[],_}|_] -> erlang:raise(error,notsup,ST); _ -> erlang:raise(error,undef,ST) end end. -spec info(Socket) -> socket_info() when Socket :: socket(). %% info(?socket(SockRef)) when is_reference(SockRef) -> prim_socket:info(SockRef); info(Socket) -> erlang:error(badarg, [Socket]). %% =========================================================================== %% %% monitor - Monitor a socket %% %% If a socket "dies", a down message, similar to erlang:monitor, will be %% sent to the requesting process: %% %% {'DOWN', MonitorRef, socket, Socket, Info} %% %% =========================================================================== -spec monitor(Socket) -> reference() when Socket :: socket(). monitor(?socket(SockRef) = Socket) when is_reference(SockRef) -> case prim_socket:setopt(SockRef, {otp, use_registry}, true) of ok -> socket_registry:monitor(Socket); {error, closed = SReason} -> MRef = make_ref(), self() ! {'DOWN', MRef, socket, Socket, SReason}, MRef end; monitor(Socket) -> erlang:error(badarg, [Socket]). %% =========================================================================== %% %% cancel_monitor - Cancel a socket monitor %% %% If MRef is a reference that the socket obtained %% by calling monitor/1, this monitoring is turned off. %% If the monitoring is already turned off, nothing happens. %% %% =========================================================================== -spec cancel_monitor(MRef) -> boolean() when MRef :: reference(). cancel_monitor(MRef) when is_reference(MRef) -> case socket_registry:cancel_monitor(MRef) of ok -> true; {error, unknown_monitor} -> false; {error, not_owner} -> erlang:error(badarg, [MRef]); {error, Reason} -> erlang:error({invalid, Reason}) end; cancel_monitor(MRef) -> erlang:error(badarg, [MRef]). %% =========================================================================== %% %% supports - get information about what the platform "supports". %% %% Generates a list of various info about what the platform 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 <- [ioctl_requests, ioctl_flags, options, msg_flags, protocols]] ++ prim_socket:supports(). -spec supports(Key1 :: term()) -> [{Key2 :: term(), boolean() | [{Key3 :: term(), boolean()}]}]. %% 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) -> prim_socket:is_supported(Key1). %% -spec is_supported(Key1 :: term(), Key2 :: term()) -> boolean(). is_supported(Key1, Key2) -> prim_socket:is_supported(Key1, Key2). %% %% Undocumented legacy function is_supported(options, Level, Opt) when is_atom(Level), is_atom(Opt) -> is_supported(options, {Level,Opt}). %% =========================================================================== %% %% The proper socket API %% %% =========================================================================== %% =========================================================================== %% %% %% %% 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 therefore need monitor function(s): %% %% socket:monitor(Socket) %% socket:demonitor(Socket) %% %% %% %% =========================================================================== %% %% open - create an endpoint for communication %% -spec open(FD) -> {'ok', Socket} | {'error', Reason} when FD :: integer(), Socket :: socket(), Reason :: posix() | 'domain' | 'type' | 'protocol'. 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() | integer(), 'type' => type() | integer(), 'protocol' => 'default' | protocol() | integer(), 'dup' => boolean(), 'debug' => boolean(), 'use_registry' => boolean()}, Socket :: socket(), Reason :: posix() | 'domain' | 'type' | 'protocol'; (Domain, Type) -> {'ok', Socket} | {'error', Reason} when Domain :: domain() | integer(), Type :: type() | integer(), Socket :: socket(), Reason :: posix() | 'protocol'. open(FD, Opts) when is_map(Opts) -> if is_integer(FD) -> case prim_socket:open(FD, Opts) of {ok, SockRef} -> Socket = ?socket(SockRef), {ok, Socket}; {error, _} = ERROR -> ERROR end; true -> erlang:error(badarg, [FD, Opts]) end; open(Domain, Type) -> open(Domain, Type, 0). -spec open(Domain, Type, Opts) -> {'ok', Socket} | {'error', Reason} when Domain :: domain() | integer(), Type :: type() | integer(), Opts :: map(), Socket :: socket(), Reason :: posix() | 'protocol'; (Domain, Type, Protocol) -> {'ok', Socket} | {'error', Reason} when Domain :: domain() | integer(), Type :: type() | integer(), Protocol :: 'default' | protocol() | integer(), Socket :: socket(), Reason :: posix() | 'protocol'. open(Domain, Type, Opts) when is_map(Opts) -> open(Domain, Type, 0, Opts); open(Domain, Type, Protocol) -> open(Domain, Type, Protocol, #{}). -spec open(Domain, Type, Protocol, Opts) -> {'ok', Socket} | {'error', Reason} when Domain :: domain() | integer(), Type :: type() | integer(), Protocol :: 'default' | protocol() | integer(), Opts :: #{'netns' => string(), 'debug' => boolean(), 'use_registry' => boolean()}, Socket :: socket(), Reason :: posix() | 'protocol'. open(Domain, Type, Protocol, Opts) when is_map(Opts) -> case prim_socket:open(Domain, Type, Protocol, Opts) of {ok, SockRef} -> Socket = ?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' | {'error', Reason} when Socket :: socket(), Addr :: sockaddr() | 'any' | 'broadcast' | 'loopback', Reason :: posix() | 'closed' | invalid(). bind(?socket(SockRef), Addr) when is_reference(SockRef) -> if 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; is_atom(Addr) -> {error, {invalid, {sockaddr, Addr}}}; true -> prim_socket:bind(SockRef, 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 :: posix() | 'closed'. bind(?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, SockAddr) -> 'ok' | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr(), Reason :: posix() | 'closed' | invalid() | 'already'. connect(Socket, SockAddr) -> connect(Socket, SockAddr, infinity). -spec connect(Socket, SockAddr, Timeout :: 'nowait') -> 'ok' | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid() | 'already' | 'not_bound' | {add_socket, posix()} | {update_connect_context, posix()}; (Socket, SockAddr, Handle :: select_handle() | completion_handle()) -> 'ok' | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid() | 'already' | 'not_bound' | {add_socket, posix()} | {update_connect_context, posix()}; (Socket, SockAddr, Timeout :: 'infinity') -> 'ok' | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr(), Reason :: posix() | 'closed' | invalid() | 'already' | 'not_bound' | {add_socket, posix()} | {update_connect_context, posix()}; (Socket, SockAddr, Timeout :: non_neg_integer()) -> 'ok' | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr(), Reason :: posix() | 'closed' | invalid() | 'already' | 'not_bound' | 'timeout' | {add_socket, posix()} | {update_connect_context, posix()}. %% %% Is it possible to connect with family = local for the (dest) sockaddr? %% connect(?socket(SockRef), SockAddr, TimeoutOrHandle) when is_reference(SockRef) -> case deadline(TimeoutOrHandle) of invalid -> erlang:error({invalid, {timeout, TimeoutOrHandle}}); nowait -> Handle = make_ref(), connect_nowait(SockRef, SockAddr, Handle); handle -> Handle = TimeoutOrHandle, connect_nowait(SockRef, SockAddr, Handle); Deadline -> connect_deadline(SockRef, SockAddr, Deadline) end; connect(Socket, SockAddr, Timeout) -> erlang:error(badarg, [Socket, SockAddr, Timeout]). connect_nowait(SockRef, SockAddr, Handle) -> case prim_socket:connect(SockRef, Handle, SockAddr) of select -> {select, ?SELECT_INFO(connect, Handle)}; completion -> {completion, ?COMPLETION_INFO(connect, Handle)}; Result -> Result end. connect_deadline(SockRef, SockAddr, Deadline) -> Ref = make_ref(), case prim_socket:connect(SockRef, Ref, SockAddr) of select -> %% Connecting... Timeout = timeout(Deadline), receive ?socket_msg(_Socket, select, Ref) -> prim_socket:connect(SockRef); ?socket_msg(_Socket, abort, {Ref, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, connect, Ref), {error, timeout} end; completion -> %% Connecting... Timeout = timeout(Deadline), receive ?socket_msg(_Socket, completion, {Ref, CompletionStatus}) -> CompletionStatus; ?socket_msg(_Socket, abort, {Ref, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, connect, Ref), {error, timeout} end; Result -> Result end. -spec connect(Socket) -> 'ok' | {'error', Reason} when Socket :: socket(), Reason :: posix() | 'closed' | invalid(). %% Finalize connect after connect(,, nowait | select_handle()) %% and received select message - see connect_deadline/3 as an example %% connect(?socket(SockRef)) when is_reference(SockRef) -> prim_socket:connect(SockRef); connect(Socket) -> erlang:error(badarg, [Socket]). %% =========================================================================== %% %% listen - listen for connections on a socket %% -spec listen(Socket) -> 'ok' | {'error', Reason} when Socket :: socket(), Reason :: posix() | 'closed' | 'not_bound'. listen(Socket) -> listen(Socket, ?ESOCK_LISTEN_BACKLOG_DEFAULT). -spec listen(Socket, Backlog) -> 'ok' | {'error', Reason} when Socket :: socket(), Backlog :: integer(), Reason :: posix() | 'closed'. listen(?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(ListenSocket) -> {'ok', Socket} | {'error', Reason} when ListenSocket :: socket(), Socket :: socket(), Reason :: posix() | 'closed' | invalid(). accept(ListenSocket) -> accept(ListenSocket, ?ESOCK_ACCEPT_TIMEOUT_DEFAULT). -spec accept(ListenSocket, Timeout :: 'nowait') -> {'ok', Socket} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when ListenSocket :: socket(), Socket :: socket(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | closed | invalid() | {create_accept_socket, posix()} | {add_accept_socket, posix()} | {update_accept_context, posix()}; (ListenSocket, Handle :: select_handle() | completion_handle()) -> {'ok', Socket} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when ListenSocket :: socket(), Socket :: socket(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid() | {create_accept_socket, posix()} | {add_socket, posix()} | {update_accept_context, posix()}; (ListenSocket, Timeout :: 'infinity') -> {'ok', Socket} | {'error', Reason} when ListenSocket :: socket(), Socket :: socket(), Reason :: posix() | 'closed' | invalid() | {create_accept_socket, posix()} | {add_socket, posix()} | {update_accept_context, posix()}; (ListenSocket, Timeout :: non_neg_integer()) -> {'ok', Socket} | {'error', Reason} when ListenSocket :: socket(), Socket :: socket(), Reason :: posix() | 'closed' | invalid() | 'timeout' | {create_accept_socket, posix()} | {add_socket, posix()} | {update_accept_context, posix()}. accept(?socket(LSockRef), Timeout) when is_reference(LSockRef) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), accept_nowait(LSockRef, Handle); handle -> Handle = Timeout, accept_nowait(LSockRef, Handle); Deadline -> accept_deadline(LSockRef, Deadline) end; accept(ListenSocket, Timeout) -> erlang:error(badarg, [ListenSocket, Timeout]). accept_nowait(LSockRef, Handle) -> case prim_socket:accept(LSockRef, Handle) of select -> {select, ?SELECT_INFO(accept, Handle)}; completion -> {completion, ?COMPLETION_INFO(accept, Handle)}; Result -> accept_result(LSockRef, Handle, 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 ?socket_msg(?socket(LSockRef), select, AccRef) -> accept_deadline(LSockRef, Deadline); ?socket_msg(_Socket, abort, {AccRef, Reason}) -> {error, Reason} after Timeout -> _ = cancel(LSockRef, accept, AccRef), {error, timeout} end; completion -> %% 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 %% CompletionStatus = {ok, Socket} | {error, Reason} ?socket_msg(?socket(LSockRef), completion, {AccRef, CompletionStatus}) -> CompletionStatus; ?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 = ?socket(SockRef), {ok, Socket}; {error, _} = ERROR -> %% Just to be on the safe side... _ = cancel(LSockRef, accept, AccRef), ERROR end. %% =========================================================================== %% %% send, sendto, sendmsg - send a message on a socket %% -spec send(Socket, Data) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(). send(Socket, Data) -> send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, ?ESOCK_SEND_TIMEOUT_DEFAULT). -spec send(Socket, Data, Flags) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Handle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(). send(Socket, Data, Flags_Cont) when is_list(Flags_Cont); is_tuple(Flags_Cont) -> send(Socket, Data, Flags_Cont, ?ESOCK_SEND_TIMEOUT_DEFAULT); send(Socket, Data, Timeout) -> send(Socket, Data, ?ESOCK_SEND_FLAGS_DEFAULT, Timeout). -spec send(Socket, Data, Flags, Handle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Flags :: [msg_flag() | integer()], RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Flags, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Flags :: [msg_flag() | integer()], RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Flags, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Flags, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, SelectHandle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, SelectHandle :: select_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(). send(?socket(SockRef), Data, ?SELECT_INFO(SelectTag, _) = Cont, Timeout) when is_reference(SockRef), is_binary(Data) -> case SelectTag of {send, ContData} -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> SelectHandle = make_ref(), send_nowait_cont(SockRef, Data, ContData, SelectHandle); handle -> SelectHandle = Timeout, send_nowait_cont(SockRef, Data, ContData, SelectHandle); Deadline -> HasWritten = false, send_deadline_cont( SockRef, Data, ContData, Deadline, HasWritten) end; _ -> {error, {invalid, Cont}} end; send(?socket(SockRef), Data, Flags, Timeout) when is_reference(SockRef), is_binary(Data), is_list(Flags) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), send_nowait(SockRef, Data, Flags, Handle); handle -> Handle = Timeout, send_nowait(SockRef, Data, Flags, Handle); Deadline -> send_deadline(SockRef, Data, Flags, Deadline) end; send(?socket(SockRef) = Socket, [Bin], Flags, Timeout) when is_reference(SockRef), is_binary(Bin) -> send(Socket, Bin, Flags, Timeout); send(?socket(SockRef) = Socket, Data, Flags, Timeout) when is_reference(SockRef), is_list(Data) -> try erlang:list_to_binary(Data) of Bin -> send(Socket, Bin, Flags, Timeout) catch error : badarg -> erlang:error({invalid, {data, Data}}) end; send(Socket, Data, Flags, Timeout) -> erlang:error(badarg, [Socket, Data, Flags, Timeout]). send_nowait(SockRef, Bin, Flags, Handle) -> send_common_nowait_result( Handle, send, prim_socket:send(SockRef, Bin, Flags, Handle)). %% On Windows, writes either succeed directly (it their entirety), %% they are scheduled (completion) or they fail. *No* partial success, %% and therefor no need to handle theme here (in cont). send_nowait_cont(SockRef, Bin, Cont, SelectHandle) -> send_common_nowait_result( SelectHandle, send, prim_socket:send(SockRef, Bin, Cont, SelectHandle)). send_deadline(SockRef, Bin, Flags, Deadline) -> Handle = make_ref(), HasWritten = false, send_common_deadline_result( SockRef, Bin, Handle, Deadline, HasWritten, send, fun send_deadline_cont/5, prim_socket:send(SockRef, Bin, Flags, Handle)). send_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten) -> Handle = make_ref(), send_common_deadline_result( SockRef, Bin, Handle, Deadline, HasWritten, send, fun send_deadline_cont/5, prim_socket:send(SockRef, Bin, Cont, Handle)). -compile({inline, [send_common_nowait_result/3]}). send_common_nowait_result(Handle, Op, Result) -> case Result of completion -> {completion, ?COMPLETION_INFO(Op, Handle)}; {select, ContData} -> {select, ?SELECT_INFO({Op, ContData}, Handle)}; {select, Data, ContData} -> {select, {?SELECT_INFO({Op, ContData}, Handle), Data}}; %% Result -> Result end. -compile({inline, [send_common_deadline_result/8]}). send_common_deadline_result( SockRef, Data, Handle, Deadline, HasWritten, Op, Fun, SendResult) -> %% case SendResult of {select, Cont} -> %% Would block, wait for continuation Timeout = timeout(Deadline), receive ?socket_msg(_Socket, select, Handle) -> Fun(SockRef, Data, Cont, Deadline, HasWritten); ?socket_msg(_Socket, abort, {Handle, Reason}) -> send_common_error(Reason, Data, HasWritten) after Timeout -> _ = cancel(SockRef, Op, Handle), send_common_error(timeout, Data, HasWritten) end; {select, Data_1, Cont} -> %% Partial send success, wait for continuation Timeout = timeout(Deadline), receive ?socket_msg(_Socket, select, Handle) -> Fun(SockRef, Data_1, Cont, Deadline, true); ?socket_msg(_Socket, abort, {Handle, Reason}) -> send_common_error(Reason, Data_1, true) after Timeout -> _ = cancel(SockRef, Op, Handle), send_common_error(timeout, Data_1, true) end; completion -> %% Would block, wait for continuation Timeout = timeout(Deadline), receive ?socket_msg(_Socket, completion, {Handle, CompletionStatus}) -> CompletionStatus; ?socket_msg(_Socket, abort, {Handle, Reason}) -> send_common_error(Reason, Data, false) after Timeout -> _ = cancel(SockRef, Op, Handle), send_common_error(timeout, Data, false) end; %% {error, {_Reason, RestIOV}} = Error when is_list(RestIOV) -> Error; {error, Reason} -> send_common_error(Reason, Data, HasWritten); Result -> Result end. send_common_error(Reason, Data, HasWritten) -> case HasWritten of false -> %% We have not managed to send any data; %% do not return what remains {error, Reason}; true -> %% Error on subsequent send - we have sent some data; %% return the remaining case Data of Bin when is_binary(Bin) -> {error, {Reason, Bin}}; IOVec when is_list(IOVec) -> {error, {Reason, IOVec}}; #{iov := IOVec} = _Msg -> {error, {Reason, IOVec}} end end. %% --------------------------------------------------------------------------- %% -spec sendto(Socket, Data, Dest) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(). sendto(Socket, Data, Dest_Cont) -> sendto(Socket, Data, Dest_Cont, ?ESOCK_SENDTO_FLAGS_DEFAULT). -spec sendto(Socket, Data, Dest, Flags) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Handle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, SelectHandle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, SelectHandle :: select_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), Cont :: select_info(), RestData :: binary(), Reason :: posix() | 'closed' | invalid(). sendto(Socket, Data, Dest, Flags) when is_list(Flags) -> sendto(Socket, Data, Dest, Flags, ?ESOCK_SENDTO_TIMEOUT_DEFAULT); sendto( ?socket(SockRef) = Socket, Data, ?SELECT_INFO(SelectTag, _) = Cont, Timeout) when is_reference(SockRef) -> case SelectTag of {sendto, ContData} -> case Data of Bin when is_binary(Bin) -> sendto_timeout_cont(SockRef, Bin, ContData, Timeout); [Bin] when is_binary(Bin) -> sendto_timeout_cont(SockRef, Bin, ContData, Timeout); IOV when is_list(IOV) -> try erlang:list_to_binary(IOV) of Bin -> sendto_timeout_cont( SockRef, Bin, ContData, Timeout) catch error : badarg -> erlang:error({invalid, {data, Data}}) end; _ -> erlang:error(badarg, [Socket, Data, Cont, Timeout]) end; _ -> {error, {invalid, Cont}} end; sendto(Socket, Data, Dest, Timeout) -> sendto(Socket, Data, Dest, ?ESOCK_SENDTO_FLAGS_DEFAULT, Timeout). -spec sendto(Socket, Data, Dest, Flags, Handle :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), Flags :: [msg_flag() | integer()], RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Flags, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), Flags :: [msg_flag() | integer()], RestData :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Flags, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Dest, Flags, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: iodata(), Dest :: sockaddr(), Flags :: [msg_flag() | integer()], RestData :: binary(), Reason :: posix() | 'closed' | invalid(). sendto(?socket(SockRef), Data, Dest, Flags, Timeout) when is_reference(SockRef), is_binary(Data), is_list(Flags) -> %% case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> SelectHandle = make_ref(), sendto_nowait(SockRef, Data, Dest, Flags, SelectHandle); handle -> Handle = Timeout, sendto_nowait(SockRef, Data, Dest, Flags, Handle); Deadline -> HasWritten = false, sendto_deadline(SockRef, Data, Dest, Flags, Deadline, HasWritten) end; sendto(?socket(SockRef) = Socket, [Bin], Dest, Flags, Timeout) when is_reference(SockRef), is_binary(Bin) -> sendto(Socket, Bin, Dest, Flags, Timeout); sendto(?socket(SockRef) = Socket, Data, Dest, Flags, Timeout) when is_reference(SockRef), is_list(Data) -> try erlang:list_to_binary(Data) of Bin -> sendto(Socket, Bin, Dest, Flags, Timeout) catch error : badarg -> erlang:error({invalid, {data, Data}}) end; sendto(Socket, Data, Dest, Flags, Timeout) -> erlang:error(badarg, [Socket, Data, Dest, Flags, Timeout]). sendto_timeout_cont(SockRef, Bin, Cont, Timeout) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> SelectHandle = make_ref(), sendto_nowait_cont(SockRef, Bin, Cont, SelectHandle); handle -> Handle = Timeout, sendto_nowait_cont(SockRef, Bin, Cont, Handle); Deadline -> HasWritten = false, sendto_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten) end. sendto_nowait(SockRef, Bin, To, Flags, Handle) -> send_common_nowait_result( Handle, sendto, prim_socket:sendto(SockRef, Bin, To, Flags, Handle)). sendto_nowait_cont(SockRef, Bin, Cont, Handle) -> send_common_nowait_result( Handle, sendto, prim_socket:sendto(SockRef, Bin, Cont, Handle)). sendto_deadline(SockRef, Bin, To, Flags, Deadline, HasWritten) -> Handle = make_ref(), send_common_deadline_result( SockRef, Bin, Handle, Deadline, HasWritten, sendto, fun sendto_deadline_cont/5, prim_socket:sendto(SockRef, Bin, To, Flags, Handle)). sendto_deadline_cont(SockRef, Bin, Cont, Deadline, HasWritten) -> Handle = make_ref(), send_common_deadline_result( SockRef, Bin, Handle, Deadline, HasWritten, sendto, fun sendto_deadline_cont/5, prim_socket:sendto(SockRef, Bin, Cont, Handle)). %% --------------------------------------------------------------------------- %% %% The only part of the msg_send() 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, Msg) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(). sendmsg(Socket, Msg) -> sendmsg(Socket, Msg, ?ESOCK_SENDMSG_FLAGS_DEFAULT, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT). -spec sendmsg(Socket, Msg, Flags) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), Flags :: [msg_flag() | integer()], RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont) -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: erlang:iovec(), Cont :: select_info(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Timeout :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), RestData :: erlang:iovec(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), RestData :: erlang:iovec(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Msg :: msg_send(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(). sendmsg(Socket, Data, Flags_Cont) when is_list(Flags_Cont); is_tuple(Flags_Cont) -> sendmsg(Socket, Data, Flags_Cont, ?ESOCK_SENDMSG_TIMEOUT_DEFAULT); sendmsg(Socket, Msg, Timeout) -> sendmsg(Socket, Msg, ?ESOCK_SENDMSG_FLAGS_DEFAULT, Timeout). -spec sendmsg(Socket, Msg, Flags, Timeout :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), Flags :: [msg_flag() | integer()], RestData :: erlang:iovec(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Flags, Handle :: select_handle() | completion_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), Flags :: [msg_flag() | integer()], RestData :: erlang:iovec(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Flags, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Msg :: msg_send(), Flags :: [msg_flag() | integer()], RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Msg, Flags, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Msg :: msg_send(), Flags :: [msg_flag() | integer()], RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: 'nowait') -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: msg_send() | erlang:iovec(), Cont :: select_info(), RestData :: erlang:iovec(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, SelectHandle :: select_handle()) -> 'ok' | {'ok', RestData} | {'select', SelectInfo} | {'select', {SelectInfo, RestData}} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: msg_send() | erlang:iovec(), Cont :: select_info(), RestData :: erlang:iovec(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: 'infinity') -> 'ok' | {'ok', RestData} | {'error', Reason} | {'error', {Reason, RestData}} when Socket :: socket(), Data :: msg_send() | erlang:iovec(), Cont :: select_info(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(); (Socket, Data, Cont, Timeout :: non_neg_integer()) -> 'ok' | {'ok', RestData} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', RestData}} when Socket :: socket(), Data :: msg_send() | erlang:iovec(), Cont :: select_info(), RestData :: erlang:iovec(), Reason :: posix() | 'closed' | invalid(). sendmsg( ?socket(SockRef) = Socket, RestData, ?SELECT_INFO(SelectTag, _) = Cont, Timeout) -> %% case SelectTag of {sendmsg, ContData} -> case RestData of #{iov := IOV} -> sendmsg_timeout_cont(SockRef, IOV, ContData, Timeout); IOV when is_list(IOV) -> sendmsg_timeout_cont(SockRef, IOV, ContData, Timeout); _ -> erlang:error(badarg, [Socket, RestData, Cont, Timeout]) end; _ -> {error, {invalid, Cont}} end; sendmsg(?socket(SockRef), #{iov := IOV} = Msg, Flags, Timeout) when is_reference(SockRef), is_list(Flags) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV); handle -> Handle = Timeout, sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV); Deadline -> HasWritten = false, sendmsg_deadline(SockRef, Msg, Flags, Deadline, HasWritten, IOV) end; sendmsg(Socket, Msg, Flags, Timeout) -> erlang:error(badarg, [Socket, Msg, Flags, Timeout]). sendmsg_timeout_cont(SockRef, RestData, Cont, Timeout) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> SelectHandle = make_ref(), sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle); handle -> SelectHandle = Timeout, sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle); Deadline -> HasWritten = false, sendmsg_deadline_cont( SockRef, RestData, Cont, Deadline, HasWritten) end. sendmsg_nowait(SockRef, Msg, Flags, Handle, IOV) -> send_common_nowait_result( Handle, sendmsg, prim_socket:sendmsg(SockRef, Msg, Flags, Handle, IOV)). sendmsg_nowait_cont(SockRef, RestData, Cont, SelectHandle) -> send_common_nowait_result( SelectHandle, sendmsg, prim_socket:sendmsg(SockRef, RestData, Cont, SelectHandle)). sendmsg_deadline(SockRef, Msg, Flags, Deadline, HasWritten, IOV) -> Handle = make_ref(), send_common_deadline_result( SockRef, IOV, Handle, Deadline, HasWritten, sendmsg, fun sendmsg_deadline_cont/5, prim_socket:sendmsg(SockRef, Msg, Flags, Handle, IOV)). sendmsg_deadline_cont(SockRef, Data, Cont, Deadline, HasWritten) -> SelectHandle = make_ref(), send_common_deadline_result( SockRef, Data, SelectHandle, Deadline, HasWritten, sendmsg, fun sendmsg_deadline_cont/5, prim_socket:sendmsg(SockRef, Data, Cont, SelectHandle)). %% =========================================================================== %% %% sendfile - send a file on a socket %% sendfile(Socket, FileHandle) -> sendfile(Socket, FileHandle, 0, 0, infinity). sendfile(Socket, FileHandle, Timeout) -> sendfile(Socket, FileHandle, 0, 0, Timeout). sendfile(Socket, FileHandle_Cont, Offset, Count) -> sendfile(Socket, FileHandle_Cont, Offset, Count, infinity). -spec sendfile(Socket, Cont, Offset, Count, SelectHandle :: 'nowait') -> {'ok', BytesSent} | {'select', SelectInfo} | {'select', {SelectInfo, BytesSent}} | {'error', Reason} when Socket :: socket(), Cont :: select_info(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Cont, Offset, Count, SelectHandle :: select_handle()) -> {'ok', BytesSent} | {'select', SelectInfo} | {'select', {SelectInfo, BytesSent}} | {'error', Reason} when Socket :: socket(), Cont :: select_info(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Cont, Offset, Count, Timeout :: 'infinity') -> {'ok', BytesSent} | {'error', Reason} | {'error', {Reason, BytesSent}} when Socket :: socket(), Cont :: select_info(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), Reason :: posix() | 'closed' | invalid(); (Socket, Cont, Offset, Count, Timeout :: non_neg_integer()) -> {'ok', BytesSent} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', BytesSent}} when Socket :: socket(), Cont :: select_info(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), Reason :: posix() | 'closed' | invalid(); (Socket, FileHandle, Offset, Count, SelectHandle :: 'nowait') -> {'ok', BytesSent} | {'select', SelectInfo} | {'select', {SelectInfo, BytesSent}} | {'error', Reason} when Socket :: socket(), FileHandle :: file:fd(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, FileHandle, Offset, Count, SelectHandle :: select_handle()) -> {'ok', BytesSent} | {'select', SelectInfo} | {'select', {SelectInfo, BytesSent}} | {'error', Reason} when Socket :: socket(), FileHandle :: file:fd(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), SelectInfo :: select_info(), Reason :: posix() | 'closed' | invalid(); (Socket, FileHandle, Offset, Count, Timeout :: 'infinity') -> {'ok', BytesSent} | {'error', Reason} | {'error', {Reason, BytesSent}} when Socket :: socket(), FileHandle :: file:fd(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), Reason :: posix() | 'closed' | invalid(); (Socket, FileHandle, Offset, Count, Timeout :: non_neg_integer()) -> {'ok', BytesSent} | {'error', Reason | 'timeout'} | {'error', {Reason | 'timeout', BytesSent}} when Socket :: socket(), FileHandle :: file:fd(), Offset :: integer(), Count :: non_neg_integer(), BytesSent :: non_neg_integer(), Reason :: posix() | 'closed' | invalid(). sendfile( ?socket(SockRef) = Socket, FileHandle_Cont, Offset, Count, Timeout) when is_integer(Offset), is_integer(Count), 0 =< Count -> %% case FileHandle_Cont of #file_descriptor{module = Module} = FileHandle -> GetFRef = internal_get_nif_resource, try Module:GetFRef(FileHandle) of FRef -> State = {FRef, Offset, Count}, sendfile_int(SockRef, State, Timeout) catch %% We could just crash here, since the caller %% maybe broke the API and did not provide %% a raw file as FileHandle, i.e GetFRef %% is not implemented in Module; %% but instead handle that nicely Class : Reason : Stacktrace when Class =:= error, Reason =:= undef -> case Stacktrace of [{Module, GetFRef, Args, _} | _] when Args =:= 1; % Arity 1 tl(Args) =:= [] -> % Arity 1 erlang:error( badarg, [Socket, FileHandle_Cont, Offset, Count, Timeout]); _ -> % Re-raise erlang:raise(Class, Reason, Stacktrace) end end; ?SELECT_INFO(SelectTag, _) = Cont -> case SelectTag of {sendfile, FRef} -> State = {FRef, Offset, Count}, sendfile_int(SockRef, State, Timeout); sendfile -> State = {Offset, Count}, sendfile_int(SockRef, State, Timeout); _ -> {error, {invalid, Cont}} end; _ -> erlang:error( badarg, [Socket, FileHandle_Cont, Offset, Count, Timeout]) end; sendfile(Socket, FileHandle_Cont, Offset, Count, Timeout) -> erlang:error( badarg, [Socket, FileHandle_Cont, Offset, Count, Timeout]). sendfile_int(SockRef, State, Timeout) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> SelectHandle = make_ref(), sendfile_nowait(SockRef, State, SelectHandle); handle -> SelectHandle = Timeout, sendfile_nowait(SockRef, State, SelectHandle); Deadline -> BytesSent = 0, sendfile_deadline(SockRef, State, BytesSent, Deadline) end. -compile({inline, [prim_socket_sendfile/3]}). prim_socket_sendfile(SockRef, {FRef, Offset, Count}, SelectHandle) -> %% Start call prim_socket:sendfile(SockRef, FRef, Offset, Count, SelectHandle); prim_socket_sendfile(SockRef, {Offset, Count}, SelectHandle) -> %% Continuation call prim_socket:sendfile(SockRef, Offset, Count, SelectHandle). sendfile_nowait(SockRef, State, SelectHandle) -> case prim_socket_sendfile(SockRef, State, SelectHandle) of select -> %% Can only happen when we are enqueued after %% a send in progress so BytesSent is 0; %% wait for continuation and later repeat start call {FRef, _Offset, _Count} = State, {select, ?SELECT_INFO({sendfile, FRef}, SelectHandle)}; {select, BytesSent} -> {select, {?SELECT_INFO(sendfile, SelectHandle), BytesSent}}; %% Result -> Result end. sendfile_deadline(SockRef, State, BytesSent_0, Deadline) -> SelectHandle = make_ref(), case prim_socket_sendfile(SockRef, State, SelectHandle) of select -> %% Can only happen when we are enqueued after %% a send in progress so BytesSent is 0; %% wait for continuation and repeat start call Timeout = timeout(Deadline), receive ?socket_msg(_Socket, select, SelectHandle) -> sendfile_deadline( SockRef, State, BytesSent_0, Deadline); ?socket_msg(_Socket, abort, {SelectHandle, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, sendfile, SelectHandle), {error, timeout} end; {select, BytesSent} -> %% Partial send success; wait for continuation Timeout = timeout(Deadline), BytesSent_1 = BytesSent_0 + BytesSent, receive ?socket_msg(_Socket, select, SelectHandle) -> sendfile_deadline( SockRef, sendfile_next(BytesSent, State), BytesSent_1, Deadline); ?socket_msg(_Socket, abort, {SelectHandle, Reason}) -> {error, {Reason, BytesSent_1}} after Timeout -> _ = cancel(SockRef, sendfile, SelectHandle), {error, {timeout, BytesSent_1}} end; {error, _} = Result when tuple_size(State) =:= 3 -> Result; {error, Reason} when tuple_size(State) =:= 2 -> {error, {Reason, BytesSent_0}}; {ok, BytesSent} -> {ok, BytesSent_0 + BytesSent} end. sendfile_next(BytesSent, {_FRef, Offset, Count}) -> sendfile_next(BytesSent, Offset, Count); sendfile_next(BytesSent, {Offset, Count}) -> sendfile_next(BytesSent, Offset, Count). %% sendfile_next(BytesSent, Offset, Count) -> {Offset + BytesSent, if Count =:= 0 -> 0; BytesSent < Count -> Count - BytesSent end}. %% =========================================================================== %% %% 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} | {'error', {Reason, Data}} when Socket :: socket(), Data :: binary(), Reason :: posix() | 'closed' | invalid(). recv(Socket) -> recv(Socket, 0, ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). -spec recv(Socket, Flags) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Length) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Data :: binary(), Reason :: posix() | 'closed' | invalid(). recv(Socket, Flags) when is_list(Flags) -> recv(Socket, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT); recv(Socket, Length) when is_integer(Length) andalso (Length >= 0) -> recv(Socket, Length, ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). -spec recv(Socket, Flags, Handle :: 'nowait') -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Flags :: [msg_flag() | integer()], Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Flags :: [msg_flag() | integer()], Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: 'infinity') -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: non_neg_integer()) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid() | 'timeout'; (Socket, Length, Flags) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Handle :: 'nowait') -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Handle :: select_handle() | completion_handle()) -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Timeout :: 'infinity') -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Timeout :: non_neg_integer()) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Data :: binary(), Reason :: posix() | 'closed' | invalid() | 'timeout'. recv(Socket, Flags, Timeout) when is_list(Flags) -> recv(Socket, 0, Flags, Timeout); 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, Handle :: 'nowait') -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Flags :: [msg_flag() | integer()], Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', Data} | {'select', SelectInfo} | {'select', {SelectInfo, Data}} | {'completion', CompletionInfo} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Flags :: [msg_flag() | integer()], Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Flags, Timeout :: 'infinity') -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Length, Flags, Timeout :: non_neg_integer()) -> {'ok', Data} | {'error', Reason} | {'error', {Reason, Data}} when Socket :: socket(), Length :: non_neg_integer(), Flags :: [msg_flag() | integer()], Data :: binary(), Reason :: posix() | 'closed' | invalid() | 'timeout'. recv(?socket(SockRef), Length, Flags, Timeout) when is_reference(SockRef), is_integer(Length), Length >= 0, is_list(Flags) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), recv_nowait(SockRef, Length, Flags, Handle, <<>>); handle -> Handle = Timeout, recv_nowait(SockRef, Length, Flags, Handle, <<>>); zero -> case prim_socket:recv(SockRef, Length, Flags, zero) of ok -> {error, timeout}; Result -> Result end; 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, Handle, Acc) -> case prim_socket:recv(SockRef, Length, Flags, Handle) 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 {select, {?SELECT_INFO(recv, Handle), bincat(Acc, Bin)}}; select -> %% The caller will get a select message when there %% might be data to read if byte_size(Acc) =:= 0 -> {select, ?SELECT_INFO(recv, Handle)}; true -> {select, {?SELECT_INFO(recv, Handle), Acc}} end; completion -> %% The caller will get a completion message (with the %% result) when the data arrives. *No* further action %% is required. {completion, ?COMPLETION_INFO(recv, Handle)}; Result -> recv_result(Acc, Result) end. recv_deadline(SockRef, Length, Flags, Deadline, Acc) -> Handle = make_ref(), case prim_socket:recv(SockRef, Length, Flags, Handle) 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 ?socket_msg(?socket(SockRef), select, Handle) -> if 0 < Timeout -> %% Recv more recv_deadline( SockRef, Length - byte_size(Bin), Flags, Deadline, bincat(Acc, Bin)); true -> {error, {timeout, bincat(Acc, Bin)}} end; ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, {Reason, bincat(Acc, Bin)}} after Timeout -> _ = cancel(SockRef, recv, Handle), {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, Handle), {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 ?socket_msg(?socket(SockRef), select, Handle) -> if 0 < Timeout -> %% Retry recv_deadline( SockRef, Length, Flags, Deadline, Acc); true -> recv_error(Acc, timeout) end; ?socket_msg(_Socket, abort, {Handle, Reason}) -> recv_error(Acc, Reason) after Timeout -> _ = cancel(SockRef, recv, Handle), recv_error(Acc, timeout) end; %% completion -> %% There is nothing just now, but we will be notified when the %% data has been read (with a completion message). Timeout = timeout(Deadline), receive ?socket_msg(?socket(SockRef), completion, {Handle, {ok, _Bin} = OK}) when (Length =:= 0) -> recv_result(Acc, OK); ?socket_msg(?socket(SockRef), completion, {Handle, {ok, Bin} = OK}) when (Length =:= byte_size(Bin)) -> recv_result(Acc, OK); ?socket_msg(?socket(SockRef), completion, {Handle, {ok, Bin}}) -> if 0 < Timeout -> %% Recv more recv_deadline( SockRef, Length - byte_size(Bin), Flags, Deadline, bincat(Acc, Bin)); true -> {error, {timeout, bincat(Acc, Bin)}} end; ?socket_msg(?socket(SockRef), completion, {Handle, {error, Reason}}) -> recv_error(Acc, Reason); ?socket_msg(_Socket, abort, {Handle, Reason}) -> recv_error(Acc, Reason) after Timeout -> _ = cancel(SockRef, recv, Handle), recv_error(Acc, timeout) end; %% We got some data, but not all {ok, Bin} when (Length > byte_size(Bin)) -> Timeout = timeout(Deadline), if 0 < Timeout -> %% Recv more recv_deadline( SockRef, Length - byte_size(Bin), Flags, Deadline, bincat(Acc, Bin)); true -> {error, {timeout, bincat(Acc, Bin)}} end; %% 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_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(). recvfrom(Socket) -> recvfrom(Socket, 0). -spec recvfrom(Socket, Flags) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(). recvfrom(Socket, Flags) when is_list(Flags) -> recvfrom(Socket, 0, Flags, ?ESOCK_RECV_TIMEOUT_DEFAULT); recvfrom(Socket, BufSz) -> recvfrom(Socket, BufSz, ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). -spec recvfrom(Socket, Flags, Handle :: 'nowait') -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: 'infinity') -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: non_neg_integer()) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid() | 'timeout'; (Socket, BufSz, Flags) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Handle :: 'nowait') -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Handle :: select_handle() | completion_handle()) -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Timeout :: 'infinity') -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Timeout :: non_neg_integer()) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid() | '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, Handle :: 'nowait') -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', {Source, Data}} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Flags, Timeout :: 'infinity') -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, Flags, Timeout :: non_neg_integer()) -> {'ok', {Source, Data}} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Source :: sockaddr_recv(), Data :: binary(), Reason :: posix() | 'closed' | invalid() | 'timeout'. recvfrom(?socket(SockRef), BufSz, Flags, Timeout) when is_reference(SockRef), is_integer(BufSz), 0 =< BufSz, is_list(Flags) -> case deadline(Timeout) of invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), recvfrom_nowait(SockRef, BufSz, Handle, Flags); handle -> Handle = Timeout, recvfrom_nowait(SockRef, BufSz, Handle, Flags); zero -> case prim_socket:recvfrom(SockRef, BufSz, Flags, zero) of ok -> {error, timeout}; Result -> recvfrom_result(Result) end; Deadline -> recvfrom_deadline(SockRef, BufSz, Flags, Deadline) end; recvfrom(Socket, BufSz, Flags, Timeout) -> erlang:error(badarg, [Socket, BufSz, Flags, Timeout]). recvfrom_nowait(SockRef, BufSz, Handle, Flags) -> case prim_socket:recvfrom(SockRef, BufSz, Flags, Handle) of select = Tag -> {Tag, ?SELECT_INFO(recvfrom, Handle)}; completion = Tag -> {Tag, ?COMPLETION_INFO(recvfrom, Handle)}; Result -> recvfrom_result(Result) end. recvfrom_deadline(SockRef, BufSz, Flags, Deadline) -> Handle = make_ref(), case prim_socket:recvfrom(SockRef, BufSz, Flags, Handle) 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 ?socket_msg(?socket(SockRef), select, Handle) -> recvfrom_deadline(SockRef, BufSz, Flags, Deadline); ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, recvfrom, Handle), {error, timeout} end; completion -> %% There is nothing just now, but we will be notified when there %% is something to read (a completion message). Timeout = timeout(Deadline), receive ?socket_msg(?socket(SockRef), completion, {Handle, CompletionStatus}) -> recvfrom_result(CompletionStatus); ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, recvfrom, Handle), {error, timeout} end; 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', Msg} | {'error', Reason} when Socket :: socket(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(). recvmsg(Socket) -> recvmsg(Socket, 0, 0, ?ESOCK_RECV_FLAGS_DEFAULT, ?ESOCK_RECV_TIMEOUT_DEFAULT). -spec recvmsg(Socket, Flags) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(); (Socket, Timeout :: 'nowait') -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Handle :: select_handle() | completion_handle()) -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Timeout :: 'infinity') -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(); (Socket, Timeout :: non_neg_integer()) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid() | '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, BufSz, CtrlSz, Timeout :: 'nowait') -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Handle :: select_handle() | completion_handle()) -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Timeout :: 'infinity') -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Timeout :: non_neg_integer()) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid() | 'timeout'. recvmsg(Socket, BufSz, CtrlSz, Timeout) -> recvmsg(Socket, BufSz, CtrlSz, ?ESOCK_RECV_FLAGS_DEFAULT, Timeout). -spec recvmsg(Socket, Flags, Timeout :: 'nowait') -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: 'infinity') -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(); (Socket, Flags, Timeout :: non_neg_integer()) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid() | 'timeout'; (Socket, BufSz, CtrlSz) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(). 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, Timeout :: 'nowait') -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Flags, Handle :: select_handle() | completion_handle()) -> {'ok', Msg} | {'select', SelectInfo} | {'completion', CompletionInfo} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), SelectInfo :: select_info(), CompletionInfo :: completion_info(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Flags, Timeout :: 'infinity') -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid(); (Socket, BufSz, CtrlSz, Flags, Timeout :: non_neg_integer()) -> {'ok', Msg} | {'error', Reason} when Socket :: socket(), BufSz :: non_neg_integer(), CtrlSz :: non_neg_integer(), Flags :: [msg_flag() | integer()], Msg :: msg_recv(), Reason :: posix() | 'closed' | invalid() | 'timeout'. recvmsg(?socket(SockRef), 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 invalid -> erlang:error({invalid, {timeout, Timeout}}); nowait -> Handle = make_ref(), recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, Handle); handle -> Handle = Timeout, recvmsg_nowait(SockRef, BufSz, CtrlSz, Flags, Handle); zero -> case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, zero) of ok -> {error, timeout}; Result -> recvmsg_result(Result) end; 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, Handle) -> case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, Handle) of select -> {select, ?SELECT_INFO(recvmsg, Handle)}; completion -> {completion, ?COMPLETION_INFO(recvmsg, Handle)}; Result -> recvmsg_result(Result) end. recvmsg_deadline(SockRef, BufSz, CtrlSz, Flags, Deadline) -> Handle = make_ref(), case prim_socket:recvmsg(SockRef, BufSz, CtrlSz, Flags, Handle) 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 ?socket_msg(?socket(SockRef), select, Handle) -> recvmsg_deadline( SockRef, BufSz, CtrlSz, Flags, Deadline); ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, recvmsg, Handle), {error, timeout} end; completion -> %% There is nothing just now, but we will be notified when there %% is something to read (a completion message). Timeout = timeout(Deadline), receive ?socket_msg(?socket(SockRef), completion, {Handle, CompletionStatus}) -> recvmsg_result(CompletionStatus); ?socket_msg(_Socket, abort, {Handle, Reason}) -> {error, Reason} after Timeout -> _ = cancel(SockRef, recvmsg, Handle), {error, timeout} end; Result -> recvmsg_result(Result) end. recvmsg_result(Result) -> %% ?DBG([{result, Result}]), case Result of {ok, _Msg} = 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 :: posix() | 'closed' | 'timeout'. close(?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 ?socket_msg(?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 :: 'read' | 'write' | 'read_write', Reason :: posix() | 'closed'. shutdown(?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... %% %% %% %% WE NEED TO MAKE SURE THAT THE USER DOES NOT MAKE US BLOCKING %% AS MUCH OF THE CODE EXPECTS TO BE NON-BLOCKING!! %% %% -spec setopt(socket(), SocketOption :: {Level :: 'otp', Opt :: otp_socket_option()}, _) -> 'ok' | {'error', invalid() | 'closed'}; (socket(), SocketOption :: socket_option(), _) -> 'ok' | {'error', posix() | invalid() | 'closed'}. setopt(?socket(SockRef), SocketOption, Value) when is_reference(SockRef) -> prim_socket:setopt(SockRef, SocketOption, Value); setopt(Socket, SocketOption, Value) -> erlang:error(badarg, [Socket, SocketOption, Value]). %% Backwards compatibility setopt(Socket, Level, Opt, Value) when is_integer(Opt), is_binary(Value) -> setopt_native(Socket, {Level,Opt}, Value); setopt(Socket, Level, Opt, Value) -> setopt(Socket, {Level,Opt}, Value). -spec setopt_native(socket(), SocketOption :: socket_option() | {Level :: level() | (NativeLevel :: integer()), NativeOpt :: integer()}, Value :: native_value()) -> 'ok' | {'error', posix() | invalid() | 'closed'}. setopt_native(?socket(SockRef), SocketOption, Value) when is_reference(SockRef) -> prim_socket:setopt_native(SockRef, SocketOption, Value); setopt_native(Socket, SocketOption, Value) -> erlang:error(badarg, [Socket, SocketOption, 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 therefore 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(), SocketOption :: {Level :: 'otp', Opt :: otp_socket_option()}) -> {'ok', Value :: term()} | {'error', invalid() | 'closed'}; (socket(), SocketOption :: socket_option()) -> {'ok', Value :: term()} | {'error', posix() | invalid() | 'closed'}. getopt(?socket(SockRef), SocketOption) when is_reference(SockRef) -> prim_socket:getopt(SockRef, SocketOption). %% Backwards compatibility getopt(Socket, Level, {NativeOpt, ValueSpec}) when is_integer(NativeOpt) -> getopt_native(Socket, {Level,NativeOpt}, ValueSpec); getopt(Socket, Level, Opt) -> getopt(Socket, {Level,Opt}). -spec getopt_native(socket(), SocketOption :: socket_option() | {Level :: level() | (NativeLevel :: integer()), NativeOpt :: integer()}, ValueType :: 'integer') -> {'ok', Value :: integer()} | {'error', posix() | invalid() | 'closed'}; (socket(), SocketOption :: socket_option() | {Level :: level() | (NativeLevel :: integer()), NativeOpt :: integer()}, ValueType :: 'boolean') -> {'ok', Value :: boolean()} | {'error', posix() | invalid() | 'closed'}; (socket(), SocketOption :: socket_option() | {Level :: level() | (NativeLevel :: integer()), NativeOpt :: integer()}, ValueSize :: non_neg_integer()) -> {'ok', Value :: binary()} | {'error', posix() | invalid() | 'closed'}; (socket(), SocketOption :: socket_option() | {Level :: level() | (NativeLevel :: integer()), NativeOpt :: integer()}, ValueSpec :: binary()) -> {'ok', Value :: binary()} | {'error', posix() | invalid() | 'closed'}. %% Compare ValueType, ValueSpec and ValueSize to native_value() %% which are the types valid to setopt_native getopt_native(?socket(SockRef), SocketOption, ValueSpec) -> prim_socket:getopt_native(SockRef, SocketOption, ValueSpec). %% =========================================================================== %% %% sockname - return the current address of the socket. %% %% -spec sockname(Socket) -> {'ok', SockAddr} | {'error', Reason} when Socket :: socket(), SockAddr :: sockaddr_recv(), Reason :: posix() | 'closed'. sockname(?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_recv(), Reason :: posix() | 'closed'. peername(?socket(SockRef)) when is_reference(SockRef) -> prim_socket:peername(SockRef); peername(Socket) -> erlang:error(badarg, [Socket]). %% =========================================================================== %% %% ioctl - control device - get requests %% %% -spec ioctl(Socket, GetRequest) -> {'ok', IFConf} | {'error', Reason} when Socket :: socket(), GetRequest :: 'gifconf', IFConf :: [#{name := string, addr := sockaddr()}], Reason :: posix() | 'closed'. %% gifconf | {gifaddr, string()} | {gifindex, string()} | {gifname, integer()} ioctl(?socket(SockRef), gifconf = GetRequest) -> prim_socket:ioctl(SockRef, GetRequest); ioctl(Socket, GetRequest) -> erlang:error(badarg, [Socket, GetRequest]). %% -spec ioctl(Socket, GetRequest, Index) -> {'ok', Name} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifname', %% Index :: integer(), %% Name :: string(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', Index} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifindex', %% Name :: string(), %% Index :: integer(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', Addr} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifaddr', %% Name :: string(), %% Addr :: sockaddr(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', DestAddr} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifdstaddr', %% Name :: string(), %% DestAddr :: sockaddr(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', BroadcastAddr} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifbrdaddr', %% Name :: string(), %% BroadcastAddr :: sockaddr(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', Netmask} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifnetmask', %% Name :: string(), %% Netmask :: sockaddr(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', HWAddr} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifhwaddr', %% Name :: string(), %% HWAddr :: sockaddr(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', MTU} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifmtu', %% Name :: string(), %% MTU :: integer(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', TransmitQLen} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'giftxqlen', %% Name :: string(), %% TransmitQLen :: integer(), %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', Flags} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifflags', %% Name :: string(), %% Flags :: [ioctl_device_flag() | integer()], %% Reason :: posix() | 'closed'; %% (Socket, GetRequest, Name) -> {'ok', DevMap} | {'error', Reason} when %% Socket :: socket(), %% GetRequest :: 'gifmap', %% Name :: string(), %% DevMap :: ioctl_device_map(), %% Reason :: posix() | 'closed'. -spec ioctl(Socket, GetRequest, NameOrIndex) -> {'ok', Result} | {'error', Reason} when Socket :: socket(), GetRequest :: 'gifname' | 'gifindex' | 'gifaddr' | 'gifdstaddr' | 'gifbrdaddr' | 'gifnetmask' | 'gifhwaddr' | 'gifmtu' | 'giftxqlen' | 'gifflags', NameOrIndex :: string() | integer(), Result :: term(), Reason :: posix() | 'closed'. ioctl(?socket(SockRef), gifname = GetRequest, Index) when is_integer(Index) -> prim_socket:ioctl(SockRef, GetRequest, Index); ioctl(?socket(SockRef), gifindex = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifaddr = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifdstaddr = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifbrdaddr = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifnetmask = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifmtu = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifhwaddr = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), giftxqlen = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifflags = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(?socket(SockRef), gifmap = GetRequest, Name) when is_list(Name) -> prim_socket:ioctl(SockRef, GetRequest, Name); ioctl(Socket, GetRequest, Arg) -> erlang:error(badarg, [Socket, GetRequest, Arg]). -spec ioctl(Socket, SetRequest, Name, Value) -> 'ok' | {'error', Reason} when Socket :: socket(), SetRequest :: 'sifflags' | 'sifaddr' | 'sifdstaddr' | 'sifbrdaddr' | 'sifnetmask' | 'sifhwaddr' | 'gifmtu' | 'siftxqlen', Name :: string(), Value :: term(), Reason :: posix() | 'closed'. ioctl(?socket(SockRef), sifflags = SetRequest, Name, Flags) when is_list(Name) andalso is_map(Flags) -> prim_socket:ioctl(SockRef, SetRequest, Name, Flags); ioctl(?socket(SockRef), sifaddr = SetRequest, Name, Addr) when is_list(Name) andalso is_map(Addr) -> prim_socket:ioctl(SockRef, SetRequest, Name, prim_socket:enc_sockaddr(Addr)); ioctl(?socket(SockRef), sifdstaddr = SetRequest, Name, DstAddr) when is_list(Name) andalso is_map(DstAddr) -> prim_socket:ioctl(SockRef, SetRequest, Name, prim_socket:enc_sockaddr(DstAddr)); ioctl(?socket(SockRef), sifbrdaddr = SetRequest, Name, BrdAddr) when is_list(Name) andalso is_map(BrdAddr) -> prim_socket:ioctl(SockRef, SetRequest, Name, prim_socket:enc_sockaddr(BrdAddr)); ioctl(?socket(SockRef), sifnetmask = SetRequest, Name, NetMask) when is_list(Name) andalso is_map(NetMask) -> prim_socket:ioctl(SockRef, SetRequest, Name, prim_socket:enc_sockaddr(NetMask)); ioctl(?socket(SockRef), sifmtu = SetRequest, Name, MTU) when is_list(Name) andalso is_integer(MTU) -> prim_socket:ioctl(SockRef, SetRequest, Name, MTU); ioctl(?socket(SockRef), siftxqlen = SetRequest, Name, QLen) when is_list(Name) andalso is_integer(QLen) -> prim_socket:ioctl(SockRef, SetRequest, Name, QLen); ioctl(Socket, SetRequest, Arg1, Arg2) -> erlang:error(badarg, [Socket, SetRequest, Arg1, Arg2]). %% =========================================================================== %% %% 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 :: 'closed' | invalid(); (Socket, CompletionInfo) -> 'ok' | {'error', Reason} when Socket :: socket(), CompletionInfo :: completion_info(), Reason :: 'closed' | invalid(). cancel(?socket(SockRef), ?SELECT_INFO(SelectTag, SelectHandle) = SelectInfo) when is_reference(SockRef) -> case SelectTag of {Op, _} when is_atom(Op) -> ok; Op when is_atom(Op) -> ok end, case cancel(SockRef, Op, SelectHandle) of ok -> ok; invalid -> {error, {invalid, SelectInfo}}; Result -> Result end; cancel(?socket(SockRef), ?COMPLETION_INFO(CompletionTag, CompletionHandle) = CompletionInfo) when is_reference(SockRef) -> case CompletionTag of {Op, _} when is_atom(Op) -> ok; Op when is_atom(Op) -> ok end, case cancel(SockRef, Op, CompletionHandle) of ok -> ok; invalid -> {error, {invalid, CompletionInfo}}; Result -> Result end; cancel(Socket, Info) -> erlang:error(badarg, [Socket, Info]). cancel(SockRef, Op, Handle) -> case prim_socket:cancel(SockRef, Op, Handle) of select_sent -> _ = flush_select_msg(SockRef, Handle), _ = flush_abort_msg(SockRef, Handle), ok; not_found -> _ = flush_completion_msg(SockRef, Handle), _ = flush_abort_msg(SockRef, Handle), invalid; Result -> %% Since we do not actually if we are using %% select or completion here, so flush both... _ = flush_select_msg(SockRef, Handle), _ = flush_completion_msg(SockRef, Handle), _ = flush_abort_msg(SockRef, Handle), Result end. flush_select_msg(SockRef, Ref) -> receive ?socket_msg(?socket(SockRef), select, Ref) -> ok after 0 -> ok end. flush_completion_msg(SockRef, Ref) -> receive ?socket_msg(?socket(SockRef), completion, {Ref, Result}) -> Result after 0 -> ok end. flush_abort_msg(SockRef, Ref) -> receive ?socket_msg(?socket(SockRef), abort, {Ref, Reason}) -> Reason after 0 -> ok end. %% =========================================================================== %% %% Misc utility functions %% %% =========================================================================== deadline(Timeout) -> case Timeout of nowait -> Timeout; infinity -> Timeout; Handle when is_reference(Handle) -> handle; 0 -> zero; _ when is_integer(Timeout), 0 < Timeout -> timestamp() + Timeout; _ -> invalid end. timeout(Deadline) -> case Deadline of %% nowait | handle shall not be passed here %% 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) -> <>. f(F, A) -> lists:flatten(io_lib:format(F, A)). %% mq() -> %% pi(messages). %% pi(Item) -> %% {Item, Val} = process_info(self(), Item), %% Val. %% 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). %% 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]).