handle partial write()'s without data loss

This commit is contained in:
Gerhard Rieger 2012-07-22 17:23:29 +02:00
parent 1c13486bd6
commit 62b43211eb
9 changed files with 108 additions and 36 deletions

View file

@ -57,6 +57,14 @@ corrections:
process crash when socat prints info about an unnamed unix domain process crash when socat prints info about an unnamed unix domain
socket socket
Michal Soltys reported the following problem and provided an initial
patch: when socat was interrupted, e.g. by SIGSTOP, and resumed during
data transfer only parts of the data might have been written.
Option o-nonblock in combination with large transfer block sizes
may result in partial writes and/or EAGAIN errors that were not handled
properly but resulted in data loss or process termination.
docu mentions option so-bindtodev but correct name is so-bindtodevice. docu mentions option so-bindtodev but correct name is so-bindtodevice.
Thanks to Jim Zimmerman for reporting. Thanks to Jim Zimmerman for reporting.

View file

@ -16,6 +16,40 @@
#include "utils.h" #include "utils.h"
#include "sysutils.h" #include "sysutils.h"
/* Substitute for Write():
Try to write all bytes before returning; this handles EINTR,
EAGAIN/EWOULDBLOCK, and partial write situations. The drawback is that this
function might block even with O_NONBLOCK option.
Returns <0 on unhandled error, errno valid
Will only return <0 or bytes
*/
ssize_t writefull(int fd, const void *buff, size_t bytes) {
size_t writt = 0;
ssize_t chk;
while (1) {
chk = Write(fd, (const char *)buff + writt, bytes - writt);
if (chk < 0) {
switch (errno) {
case EINTR:
case EAGAIN:
#if EAGAIN != EWOULDBLOCK
case EWOULDBLOCK:
#endif
Warn4("write(%d, %p, "F_Zu"): %s", fd, (const char *)buff+writt, bytes-writt, strerror(errno));
Sleep(1); continue;
default: return -1;
}
} else if (writt+chk < bytes) {
Warn4("write(%d, %p, "F_Zu"): only wrote "F_Zu" bytes, trying to continue ",
fd, (const char *)buff+writt, bytes-writt, chk);
writt += chk;
} else {
writt = bytes;
break;
}
}
return writt;
}
#if WITH_UNIX #if WITH_UNIX
void socket_un_init(struct sockaddr_un *sa) { void socket_un_init(struct sockaddr_un *sa) {

View file

@ -41,6 +41,8 @@ struct xiorange {
} ; } ;
#endif /* _WITH_SOCKET */ #endif /* _WITH_SOCKET */
extern ssize_t writefull(int fd, const void *buff, size_t bytes);
#if _WITH_SOCKET #if _WITH_SOCKET
extern socklen_t socket_init(int af, union sockaddr_union *sa); extern socklen_t socket_init(int af, union sockaddr_union *sa);
#endif #endif

44
test.sh
View file

@ -10868,6 +10868,50 @@ esac
N=$((N+1)) N=$((N+1))
# incomplete writes were reported but led to data loss
NAME=INCOMPLETE_WRITE
case "$TESTS" in
*%functions%*|*%bugs%*|*%$NAME%*)
TEST="$NAME: check if incomplete writes are handled properly"
# write to a nonblocking fd a block that is too large for atomic write
# and check if all data arrives
if ! eval $NUMCOND; then :; else
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tp="$td/test$N.pipe"
tw="$td/test$N.wc-c"
# this is the size we write() in one call; data is never stored on disk, so
# make it large enough to exceed any atomic write size; but higher number might
# take much time
bytes=100000 # for Linux 2.6.? this must be >65536
CMD0="$SOCAT $opts -u PIPE:$tp STDOUT"
CMD1="$SOCAT $opts -u -b $bytes OPEN:/dev/zero,readbytes=$bytes FILE:$tp,o-nonblock"
printf "test $F_n $TEST... " $N
$CMD0 2>"${te}0" |wc -c >"$tw" &
pid=$!
waitfile "$tp"
$CMD1 2>"${te}1" >"${tf}1"
rc1=$?
wait
if [ $rc1 -ne 0 ]; then
$PRINTF "$NO_RESULT\n"
numCANT=$((numCANT+1))
elif [ ! -e "$tw" ]; then
$PRINTF "$NO_RESULT\n"
numCANT=$((numCANT+1))
elif [ "$bytes" -eq $(cat "$tw") ]; then
$PRINTF "$OK\n"
numOK=$((numOK+1))
else
$PRINTF "$FAILED\n"
echo "transferred only $(cat $tw) of $bytes bytes" >&2
numFAIL=$((numFAIL+1))
fi
fi # NUMCOND
;;
esac
N=$((N+1))
# socat up to 1.7.2.0 and 2.0.0-b4 had a bug in xioscan_readline() that could # socat up to 1.7.2.0 and 2.0.0-b4 had a bug in xioscan_readline() that could
# be exploited # be exploited

View file

@ -371,10 +371,7 @@ int _xioopen_proxy_connect(struct single *xfd,
* xiosanitize(request, strlen(request), textbuff) = '\0'; * xiosanitize(request, strlen(request), textbuff) = '\0';
Info1("sending \"%s\"", textbuff); Info1("sending \"%s\"", textbuff);
/* write errors are assumed to always be hard errors, no retry */ /* write errors are assumed to always be hard errors, no retry */
do { if (writefull(xfd->wfd, request, strlen(request)) < 0) {
sresult = Write(xfd->wfd, request, strlen(request));
} while (sresult < 0 && errno == EINTR);
if (sresult < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s", Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, request, strlen(request), strerror(errno)); xfd->wfd, request, strlen(request), strerror(errno));
if (Close(xfd->wfd) < 0) { if (Close(xfd->wfd) < 0) {
@ -404,10 +401,7 @@ int _xioopen_proxy_connect(struct single *xfd,
*next = '\0'; *next = '\0';
Info1("sending \"%s\\r\\n\"", header); Info1("sending \"%s\\r\\n\"", header);
*next++ = '\r'; *next++ = '\n'; *next++ = '\0'; *next++ = '\r'; *next++ = '\n'; *next++ = '\0';
do { if (writefull(xfd->wfd, header, strlen(header)) < 0) {
sresult = Write(xfd->wfd, header, strlen(header));
} while (sresult < 0 && errno == EINTR);
if (sresult < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s", Msg4(level, "write(%d, %p, "F_Zu"): %s",
xfd->wfd, header, strlen(header), strerror(errno)); xfd->wfd, header, strlen(header), strerror(errno));
if (Close(xfd->wfd/*!*/) < 0) { if (Close(xfd->wfd/*!*/) < 0) {
@ -420,10 +414,14 @@ int _xioopen_proxy_connect(struct single *xfd,
} }
Info("sending \"\\r\\n\""); Info("sending \"\\r\\n\"");
do { if (writefull(xfd->wfd, "\r\n", 2) < 0) {
sresult = Write(xfd->wfd, "\r\n", 2); Msg2(level, "write(%d, %p, "F_Zu"): %s",
} while (sresult < 0 && errno == EINTR); xfd->wfd, strerror(errno));
/*! */ if (Close(xfd->wfd) < 0) {
Info2("close(%d): %s", xfd->wfd, strerror(errno));
}
return STAT_RETRYLATER;
}
/* request is kept for later error messages */ /* request is kept for later error messages */
*strstr(request, " HTTP") = '\0'; *strstr(request, " HTTP") = '\0';

View file

@ -185,9 +185,7 @@ ssize_t xioread_readline(struct single *pipe, void *buff, size_t bufsiz) {
/* we must carriage return, because readline will first print the /* we must carriage return, because readline will first print the
prompt */ prompt */
ssize_t writt; ssize_t writt;
do { writt = writefull(pipe->rfd, "\r", 1);
writt = Write(pipe->rfd, "\r", 1);
} while (writt < 0 && errno == EINTR);
if (writt < 0) { if (writt < 0) {
Warn2("write(%d, \"\\r\", 1): %s", Warn2("write(%d, \"\\r\", 1): %s",
pipe->rfd, strerror(errno)); pipe->rfd, strerror(errno));

View file

@ -377,10 +377,7 @@ int _xioopen_socks4_connect(struct single *xfd,
} }
} }
#endif /* WITH_MSGLEVEL <= E_DEBUG */ #endif /* WITH_MSGLEVEL <= E_DEBUG */
do { if (writefull(wfd, sockhead, headlen) < 0) {
result = Write(wfd, sockhead, headlen);
} while (result < 0 && errno == EINTR);
if (result < 0) {
Msg4(level, "write(%d, %p, "F_Zu"): %s", Msg4(level, "write(%d, %p, "F_Zu"): %s",
wfd, sockhead, headlen, strerror(errno)); wfd, sockhead, headlen, strerror(errno));
if (Close(wfd) < 0) { if (Close(wfd) < 0) {

View file

@ -1,5 +1,5 @@
/* source: xiolockfile.c */ /* source: xiolockfile.c */
/* Copyright Gerhard Rieger 2005-2006 */ /* Copyright Gerhard Rieger 2005-2012 */
/* Published under the GNU General Public License V.2, see file COPYING */ /* Published under the GNU General Public License V.2, see file COPYING */
/* this file contains socats explicit locking mechanisms */ /* this file contains socats explicit locking mechanisms */
@ -52,7 +52,10 @@ int xiogetlock(const char *lockfile) {
pid = Getpid(); pid = Getpid();
bytes = sprintf(pidbuf, F_pid, pid); bytes = sprintf(pidbuf, F_pid, pid);
Write(fd, pidbuf, bytes); if (writefull(fd, pidbuf, bytes) < 0) {
Error4("write(%d, %p, "F_Zu"): %s", fd, pidbuf, bytes, strerror(errno));
return -1;
}
Close(fd); Close(fd);
/* Chmod(lockfile, 0600); */ /* Chmod(lockfile, 0600); */

View file

@ -1,5 +1,5 @@
/* source: xiowrite.c */ /* source: xiowrite.c */
/* Copyright Gerhard Rieger 2001-2008 */ /* Copyright Gerhard Rieger 2001-2012 */
/* Published under the GNU General Public License V.2, see file COPYING */ /* Published under the GNU General Public License V.2, see file COPYING */
/* this is the source of the extended write function */ /* this is the source of the extended write function */
@ -53,9 +53,7 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
switch (pipe->dtype & XIODATA_WRITEMASK) { switch (pipe->dtype & XIODATA_WRITEMASK) {
case XIOWRITE_STREAM: case XIOWRITE_STREAM:
do { writt = writefull(pipe->wfd, buff, bytes);
writt = Write(fd, buff, bytes);
} while (writt < 0 && errno == EINTR);
if (writt < 0) { if (writt < 0) {
_errno = errno; _errno = errno;
switch (_errno) { switch (_errno) {
@ -74,10 +72,6 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
errno = _errno; errno = _errno;
return -1; return -1;
} }
if ((size_t)writt < bytes) {
Warn2("write() only wrote "F_Zu" of "F_Zu" bytes",
writt, bytes);
}
break; break;
#if _WITH_SOCKET #if _WITH_SOCKET
@ -125,9 +119,7 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
case XIOWRITE_PIPE: case XIOWRITE_PIPE:
case XIOWRITE_2PIPE: case XIOWRITE_2PIPE:
do { writt = Write(pipe->wfd, buff, bytes);
writt = Write(fd, buff, bytes);
} while (writt < 0 && errno == EINTR);
_errno = errno; _errno = errno;
if (writt < 0) { if (writt < 0) {
Error4("write(%d, %p, "F_Zu"): %s", Error4("write(%d, %p, "F_Zu"): %s",
@ -135,10 +127,6 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
errno = _errno; errno = _errno;
return -1; return -1;
} }
if ((size_t)writt < bytes) {
Warn2("write() only wrote "F_Zu" of "F_Zu" bytes",
writt, bytes);
}
break; break;
#if WITH_TEST #if WITH_TEST