Skip to content

Commit

Permalink
Return number of bytes for partial writes
Browse files Browse the repository at this point in the history
The write functions (sendto/4, sendmsg/5, write/2, writev/2) have had a
long standing bug where failure to write the complete buffer was
silently ignored.

Return a tuple containing the number of bytes written if the complete
buffer could not be sent. Writes can be continued using something like:

    write_exact(FD, Buf) ->
        case procket:write(FD, Buf) of
            ok ->
                ok;
            {ok, N} - >
                {_:N/bytes, Rest/binary} = Buf,
                write_exact(FD, Rest);
            Error ->
                Error
        end.

Successful writes of the entire buffer still return 'ok'.
  • Loading branch information
msantos committed Aug 3, 2015
1 parent 374ee6d commit 28d1865
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 29 deletions.
38 changes: 28 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,31 +175,37 @@ procket works with any version of Erlang after R14A.

sendto(Socket, Buf) -> ok | {error, posix()}
sendto(Socket, Buf, Flags) -> ok | {error, posix()}
sendto(Socket, Buf, Flags, Sockaddr) -> ok | {error, posix()}
sendto(Socket, Buf, Flags, Sockaddr) -> ok | {ok, Size} | {error, posix()}

Types Socket = integer()
Flags = integer()
Buf = binary()
Sockaddr = binary()
Size = non_neg_integer()

See sendto(2).

In the case of a partial write, sendto/4 will return the number
of bytes written.

sendmsg(Socket, Buf, Flags, CtrlData) -> ok | {error, posix()}
sendmsg(Socket, Buf, Flags, CtrlData, Sockaddr) -> ok | {error, posix()}
sendmsg(Socket, Buf, Flags, CtrlData, Sockaddr) -> ok | {ok, Size} | {error, posix()}

Types Socket = integer()
Size = ulong()
CtrlDataSize = ulong()
Flags = integer()
Buf = binary()
Sockaddr = binary()
Flags = integer()
CtrlData = [{integer(), integer(), binary()}]
Sockaddr = binary()
Size = non_neg_integer()

See sendmsg(2) and cmsg(3).

The control data, if any, is sent as a list of 3-tuples consisting of the cmsg
level, type and data fields.

In the case of a partial write, sendmsg/5 will return the number
of bytes written.

read(FD, Length) -> {ok, Buf} | {error, posix()}

Types FD = integer()
Expand All @@ -210,17 +216,29 @@ procket works with any version of Erlang after R14A.

The returned byte_size(Buf) is the actual number of bytes read.

write(FD, Buf) -> ok | {error, posix()}
writev(FD, Bufs) -> ok | {error, posix()}
write(FD, Buf) -> ok | {ok, Size} | {error, posix()}
writev(FD, Bufs) -> ok | {ok, Size} | {error, posix()}

Types FD = integer()
Buf = Bufs | binary()
Bufs = [ binary() ]
Size = non_neg_integer()

See write(2) and writev(2).

If the second argument to write/2 is a list of binaries, writev/2
will be used.
write/2 and writev/2 will return 'ok' if the complete buffer was
written and {ok,non_neg_integer()} in the case of a partial write:

write_exact(FD, Buf) ->
case procket:write(FD, Buf) of
ok ->
ok;
{ok, N} ->
<<_:N/bytes, Rest/binary>> = Buf,
write_exact(FD, Rest);
Error ->
Error
end.

bind(Socket, Sockaddr) -> ok | {error, posix()}

Expand Down
33 changes: 21 additions & 12 deletions c_src/procket.c
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ nif_sendto(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
int sockfd = -1;
int flags = 0;
ssize_t n = 0;

ErlNifBinary buf = {0};
ErlNifBinary sa = {0};
Expand All @@ -326,12 +327,14 @@ nif_sendto(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
if (!enif_inspect_binary(env, argv[3], &sa))
return enif_make_badarg(env);

if (sendto(sockfd, buf.data, buf.size, flags,
(sa.size == 0 ? NULL : (struct sockaddr *)sa.data),
sa.size) == -1)
n = sendto(sockfd, buf.data, buf.size, flags,
(sa.size == 0 ? NULL : (struct sockaddr *)sa.data),
sa.size);

if (n < 0)
return error_tuple(env, errno);

return atom_ok;
return enif_make_tuple2(env, atom_ok, enif_make_int64(env, n));
}


Expand Down Expand Up @@ -371,6 +374,7 @@ nif_read(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
nif_write(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
{
int fd = -1;
ssize_t n = 0;

ErlNifBinary buf = {0};

Expand All @@ -380,10 +384,12 @@ nif_write(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
if (!enif_inspect_binary(env, argv[1], &buf))
return enif_make_badarg(env);

if (write(fd, buf.data, buf.size) == -1)
n = write(fd, buf.data, buf.size);

if (n < 0)
return error_tuple(env, errno);

return atom_ok;
return enif_make_tuple2(env, atom_ok, enif_make_int64(env, n));
}

#define IOVMAX 256
Expand All @@ -397,6 +403,7 @@ nif_writev(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
struct iovec iovs[IOVMAX];
int fd = -1;
unsigned iovcnt;
ssize_t n = 0;

if (!enif_get_int(env, argv[0], &fd))
return enif_make_badarg(env);
Expand All @@ -422,10 +429,12 @@ nif_writev(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
iov->iov_len = buf.size;
}

if (writev(fd, iovs, iovcnt) == -1)
n = writev(fd, iovs, iovcnt);

if (n < 0)
return error_tuple(env, errno);

return atom_ok;
return enif_make_tuple2(env, atom_ok, enif_make_int64(env, n));
}

/* 0: socket descriptor, 1: struct sockaddr */
Expand Down Expand Up @@ -845,7 +854,7 @@ nif_sendmsg(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[])
if (n < 0)
return error_tuple(env, errno);

return atom_ok;
return enif_make_tuple2(env, atom_ok, enif_make_int64(env, n));
}


Expand Down Expand Up @@ -1146,15 +1155,15 @@ static ErlNifFunc nif_funcs[] = {
{"listen", 2, nif_listen},
{"read", 2, nif_read},
{"write_nif", 2, nif_write},
{"writev", 2, nif_writev},
{"writev_nif", 2, nif_writev},

{"ioctl", 3, nif_ioctl},
{"socket_nif", 3, nif_socket},
{"recvmsg_nif", 5, nif_recvmsg},
{"sendmsg", 5, nif_sendmsg},
{"sendmsg_nif", 5, nif_sendmsg},

{"recvfrom", 4, nif_recvfrom},
{"sendto", 4, nif_sendto},
{"sendto_nif", 4, nif_sendto},
{"setsockopt_nif", 4, nif_setsockopt},

{"alloc_nif", 1, nif_alloc},
Expand Down
2 changes: 1 addition & 1 deletion src/procket.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
[{description, "Low level socket operations"},
{applications, [crypto]},
{registered, []},
{vsn, "0.6.1"}]}.
{vsn, "0.7.0"}]}.
47 changes: 41 additions & 6 deletions src/procket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@
accept/1,accept/2,
close/1,
recv/2,recvfrom/2,recvfrom/4,
sendto/2, sendto/3,sendto/4,
read/2, write/2, writev/2,
sendto/2, sendto/3, sendto/4,
read/2,
write/2, writev/2,
bind/2,
ioctl/3,
setsockopt/4,
Expand Down Expand Up @@ -152,18 +153,42 @@ sendto(Socket, Buf) ->
sendto(Socket, Buf, 0, <<>>).
sendto(Socket, Buf, Flags) ->
sendto(Socket, Buf, Flags, <<>>).
sendto(_,_,_,_) ->
sendto(Socket, Buf, Flags, Sockaddr) ->
Size = byte_size(Buf),
case sendto_nif(Socket, Buf, Flags, Sockaddr) of
{ok, Size} ->
ok;
Reply ->
Reply
end.

sendto_nif(_,_,_,_) ->
erlang:nif_error(not_implemented).

write(FD, Buf) when is_binary(Buf) ->
write_nif(FD, Buf);
Size = byte_size(Buf),
case write_nif(FD, Buf) of
{ok, Size} ->
ok;
Reply ->
Reply
end;
write(FD, Buf) when is_list(Buf) ->
writev(FD, Buf).

write_nif(_,_) ->
erlang:nif_error(not_implemented).

writev(_,_) ->
writev(FD, Buf) ->
Size = iolist_size(Buf),
case writev_nif(FD, Buf) of
{ok, Size} ->
ok;
Reply ->
Reply
end.

writev_nif(_,_) ->
erlang:nif_error(not_implemented).

recvmsg(Socket,Size,Flags,CtrlDataSize) ->
Expand All @@ -182,9 +207,19 @@ recvmsg(Socket,Size,Flags,CtrlDataSize,SockaddrSize) ->
end.
recvmsg_nif(_,_,_,_,_) ->
erlang:nif_error(not_implemented).

sendmsg(Socket,Buf,Flags,CtrlData) ->
sendmsg(Socket,Buf,Flags,CtrlData,<<>>).
sendmsg(_,_,_,_,_) ->
sendmsg(Socket,Buf,Flags,CtrlData,Sockaddr) ->
Size = byte_size(Buf),
case sendmsg_nif(Socket,Buf,Flags,CtrlData,Sockaddr) of
{ok, Size} ->
ok;
Reply ->
Reply
end.

sendmsg_nif(_,_,_,_,_) ->
erlang:nif_error(not_implemented).

setsockopt(Socket,Level,Optname,Optval) when is_atom(Level) ->
Expand Down

0 comments on commit 28d1865

Please sign in to comment.