Align buffer for read() with flag O_DIRECT

This commit is contained in:
Gerhard Rieger 2020-12-29 05:07:03 +01:00
parent de2f2c471b
commit 55518fa690
7 changed files with 124 additions and 34 deletions

View file

@ -21,6 +21,12 @@ Corrections:
Mitigated race condition of quickly terminating SYSTEM or EXEC child
processes.
Option o-direct might require alignment of read/write buffer to, e.g.,
512 bytes, Socat now takes care of this when allocating the buffer.
With this fix read() succeeds, however, write() still might fail when
not writing complete pages.
Test: O_DIRECT
Porting:
In gcc version 10 the default changed from -fcommon to -fno-common.
Consequently, linking filan and procan failed with error

View file

@ -69,6 +69,9 @@
/* Define if you have the socket function. */
#undef HAVE_SOCKET
/* Define if you have the posix_memalign function. */
#undef HAVE_PROTOTYPE_LIB_posix_memalign
/* Define if you have the strdup function. */
#undef HAVE_PROTOTYPE_LIB_strdup

View file

@ -785,6 +785,7 @@ AC_CHECK_FUNCS(cfsetispeed cfgetispeed cfsetospeed cfgetospeed)
###################################
# check for prototype and existence of functions that return a pointer
# defines in config.h: HAVE_PROTOTYPE_LIB_$1
AC_CHECK_PROTOTYPE_LIB(posix_memalign)
AC_CHECK_PROTOTYPE_LIB(strdup)
AC_CHECK_PROTOTYPE_LIB(strerror)
AC_CHECK_PROTOTYPE_LIB(strstr)
@ -794,7 +795,6 @@ AC_CHECK_PROTOTYPE_LIB(memrchr)
AC_CHECK_PROTOTYPE_LIB(if_indextoname)
AC_CHECK_PROTOTYPE_LIB(ptsname)
AC_MSG_CHECKING(for long long)
AC_CACHE_VAL(sc_cv_type_longlong,
[AC_TRY_COMPILE([],[long long s;],

73
socat.c
View file

@ -57,7 +57,7 @@ void socat_opt_hint(FILE *fd, char a, char b);
void socat_version(FILE *fd);
int socat(const char *address1, const char *address2);
int _socat(void);
int cv_newline(unsigned char **buff, ssize_t *bytes, int lineterm1, int lineterm2);
int cv_newline(unsigned char *buff, ssize_t *bytes, int lineterm1, int lineterm2);
void socat_signal(int sig);
static int socat_sigchild(struct single *file);
@ -708,7 +708,7 @@ int childleftdata(xiofile_t *xfd) {
}
int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
unsigned char **buff, size_t bufsiz, bool righttoleft);
unsigned char *buff, size_t bufsiz, bool righttoleft);
bool mayrd1; /* sock1 has read data or eof, according to poll() */
bool mayrd2; /* sock2 has read data or eof, according to poll() */
@ -765,8 +765,21 @@ int _socat(void) {
Error2("buffer size option (-b) to big - "F_Zu" (max is "F_Zu")", socat_opts.bufsiz, (SIZE_MAX-1)/2);
socat_opts.bufsiz = (SIZE_MAX-1)/2;
}
#if HAVE_PROTOTYPE_LIB_posix_memalign
/* Operations on files with flag O_DIRECT might need buffer alignment.
Without this, eg.read() fails with "Invalid argument" */
{
int _errno;
if ((_errno = Posix_memalign((void **)&buff, getpagesize(), 2*socat_opts.bufsiz+1)) != 0) {
Error1("posix_memalign(): %s", strerror(_errno));
return -1;
}
}
#else /* !HAVE_PROTOTYPE_LIB_posix_memalign */
buff = Malloc(2*socat_opts.bufsiz+1);
if (buff == NULL) return -1;
#endif /* !HAVE_PROTOTYPE_LIB_posix_memalign */
if (socat_opts.logopt == 'm' && xioinqopt('l', NULL, 0) == 'm') {
Info("switching to syslog");
@ -988,7 +1001,7 @@ int _socat(void) {
if (mayrd1 && maywr2) {
mayrd1 = false;
if ((bytes1 = xiotransfer(sock1, sock2, &buff, socat_opts.bufsiz, false))
if ((bytes1 = xiotransfer(sock1, sock2, buff, socat_opts.bufsiz, false))
< 0) {
if (errno != EAGAIN) {
closing = MAX(closing, 1);
@ -1020,7 +1033,7 @@ int _socat(void) {
if (mayrd2 && maywr1) {
mayrd2 = false;
if ((bytes2 = xiotransfer(sock2, sock1, &buff, socat_opts.bufsiz, true))
if ((bytes2 = xiotransfer(sock2, sock1, buff, socat_opts.bufsiz, true))
< 0) {
if (errno != EAGAIN) {
closing = MAX(closing, 1);
@ -1194,10 +1207,10 @@ static int
*/
/* inpipe, outpipe must be single descriptors (not dual!) */
int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
unsigned char **buff, size_t bufsiz, bool righttoleft) {
unsigned char *buff, size_t bufsiz, bool righttoleft) {
ssize_t bytes, writt = 0;
bytes = xioread(inpipe, *buff, bufsiz);
bytes = xioread(inpipe, buff, bufsiz);
if (bytes < 0) {
if (errno != EAGAIN)
XIO_RDSTREAM(inpipe)->eof = 2;
@ -1215,7 +1228,7 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
/* handle escape char */
if (XIO_RDSTREAM(inpipe)->escape != -1) {
/* check input data for escape char */
unsigned char *ptr = *buff;
unsigned char *ptr = buff;
size_t ctr = 0;
while (ctr < bytes) {
if (*ptr == XIO_RDSTREAM(inpipe)->escape) {
@ -1251,8 +1264,8 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
size_t j;
size_t N = 16;
const unsigned char *end, *s, *t;
s = *buff;
end = (*buff)+bytes;
s = buff;
end = buff+bytes;
xioprintblockheader(stderr, bytes, righttoleft);
while (s < end) {
/*! prefix? */
@ -1298,8 +1311,8 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
size_t i = 0;
xioprintblockheader(stderr, bytes, righttoleft);
while (i < (size_t)bytes) {
int c = (*buff)[i];
if (i > 0 && (*buff)[i-1] == '\n')
int c = buff[i];
if (i > 0 && buff[i-1] == '\n')
/*! prefix? */;
switch (c) {
case '\a' : fputs("\\a", stderr); break;
@ -1323,12 +1336,12 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
/* print prefix */
xioprintblockheader(stderr, bytes, righttoleft);
for (i = 0; i < bytes; ++i) {
fprintf(stderr, " %02x", (*buff)[i]);
fprintf(stderr, " %02x", buff[i]);
}
fputc('\n', stderr);
}
writt = xiowrite(outpipe, *buff, bytes);
writt = xiowrite(outpipe, buff, bytes);
if (writt < 0) {
/* EAGAIN when nonblocking but a mandatory lock is on file.
the problem with EAGAIN is that the read cannot be repeated,
@ -1355,11 +1368,9 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
/* converts the newline characters (or character sequences) from the one
specified in lineterm1 to that of lineterm2. Possible values are
LINETERM_CR, LINETERM_CRNL, LINETERM_RAW.
buff points to the malloc()'ed data, input and output. It may be subject to
realloc(). bytes specifies the number of bytes input and output */
int cv_newline(unsigned char **buff, ssize_t *bufsiz,
bytes specifies the number of bytes input and output */
int cv_newline(unsigned char *buff, ssize_t *bytes,
int lineterm1, int lineterm2) {
ssize_t *bytes = bufsiz;
/* must perform newline changes */
if (lineterm1 <= LINETERM_CR && lineterm2 <= LINETERM_CR) {
/* no change in data length */
@ -1369,23 +1380,23 @@ int cv_newline(unsigned char **buff, ssize_t *bufsiz,
} else {
from = '\r'; to = '\n';
}
z = *buff + *bytes;
p = *buff;
z = buff + *bytes;
p = buff;
while (p < z) {
if (*p == from) *p = to;
++p;
}
} else if (lineterm1 == LINETERM_CRNL) {
/* buffer becomes shorter */
/* buffer might become shorter */
unsigned char to, *s, *t, *z;
if (lineterm2 == LINETERM_RAW) {
to = '\n';
} else {
to = '\r';
}
z = *buff + *bytes;
s = t = *buff;
z = buff + *bytes;
s = t = buff;
while (s < z) {
if (*s == '\r') {
++s;
@ -1397,20 +1408,24 @@ int cv_newline(unsigned char **buff, ssize_t *bufsiz,
*t++ = *s++;
}
}
*bufsiz = t - *buff;
*bytes = t - buff;
} else {
/* buffer becomes longer, must alloc another space */
unsigned char *buf2;
/* buffer becomes longer (up to double length), must alloc another space */
static unsigned char *buf2; /*! not threadsafe */
unsigned char from; unsigned char *s, *t, *z;
if (lineterm1 == LINETERM_RAW) {
from = '\n';
} else {
from = '\r';
}
if ((buf2 = Malloc(2*socat_opts.bufsiz/*sic!*/+1)) == NULL) {
if (buf2 == NULL) {
if ((buf2 = Malloc(socat_opts.bufsiz)) == NULL) {
return -1;
}
s = *buff; t = buf2; z = *buff + *bytes;
}
memcpy(buf2, buff, *bytes);
s = buf2; t = buff; z = buf2 + *bytes;
while (s < z) {
if (*s == from) {
*t++ = '\r'; *t++ = '\n';
@ -1420,9 +1435,7 @@ int cv_newline(unsigned char **buff, ssize_t *bufsiz,
*t++ = *s++;
}
}
free(*buff);
*buff = buf2;
*bufsiz = t - buf2;;
*bytes = t - buff;;
}
return 0;
}

10
sycls.c
View file

@ -23,6 +23,16 @@
#if WITH_SYCLS
#if HAVE_PROTOTYPE_LIB_posix_memalign
int Posix_memalign(void **memptr, size_t alignment, size_t size) {
int result;
Debug3("posix_memalign(%p, "F_Zu", F_Zu)", memptr, alignment, size);
result = posix_memalign(memptr, alignment, size);
Debug1("posix_memalign(...) -> %d", result);
return result;
}
#endif /* HAVE_PROTOTYPE_LIB_posix_memalign */
mode_t Umask(mode_t mask) {
mode_t result;
int _errno;

View file

@ -11,6 +11,7 @@ struct utsname;
struct flock;
struct addrinfo;
int Posix_memalign(void **memptr, size_t alignment, size_t size);
mode_t Umask(mode_t mask);
#endif /* WITH_SYCLS */
int Open(const char *pathname, int flags, mode_t mode);
@ -175,6 +176,7 @@ void Add_history(const char *string);
#else /* !WITH_SYCLS */
#define Posix_memalign(m,a,s) posix_memalign(m,a,s)
#define Umask(m) umask(m)
#define Creat(p,m) creat(p,m)
#define Lseek(f,o,w) lseek(f,o,w)

58
test.sh
View file

@ -13614,7 +13614,7 @@ NAME=SCTP_SERVICENAME
case "$TESTS" in
*%$N%*|*%functions%*|*%socket%*|*%sctp%*|*%$NAME%*)
TEST="$NAME: Service name resolution works with SCTP"
# invoke socat with address SCTP4-CONNECT:$LOCALHOST:http; when this does fails with
# invoke socat with address SCTP4-CONNECT:$LOCALHOST:http; when this fails with
# "Connection refused", or does not fail at all, the test succeeded
if ! eval $NUMCOND; then :;
elif ! runssctp4 "$((PORT))" >/dev/null; then
@ -13647,6 +13647,62 @@ esac
N=$((N+1))
# Test the o-direct option on reading
NAME=O_DIRECT
case "$TESTS" in
*%$N%*|*%functions%*|*%engine%*|*%file%*|*%$NAME%*)
TEST="$NAME: echo via file with o-direct"
# Write data to a file and read it with options o-direct (and ignoreeof)
# When the data read is the same as the data written the test succeeded.
if ! eval $NUMCOND; then :;
elif ! testoptions o-direct >/dev/null; then
$PRINTF "test $F_n $TEST... ${YELLOW}o-direct not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
else
tf="$td/test$N.file"
to="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
$PRINTF "test $F_n $TEST... " $N
CMD="$TRACE $SOCAT $opts - $tf,o-direct,ignoreeof!!$tf"
echo "$da" |$CMD >"$to" 2>"$te"
rc=$?
if [ $rc -ne 0 ] && grep -q "Invalid argument" "$te" && [ $UNAME = Linux ]; then
case $(stat -f /tmp/gerhard/ |grep -o "Type: [^[:space:]]*" |cut -c 7-) in
ext2/ext3|xfs|reiserfs)
$PRINTF "${FAILED}\n"
echo "$CMD" >&2
cat "$te" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N" ;;
*) $PRINTF "${YELLOW}inable file system${NORMAL}\n"
numCANT=$((numCANT+1))
listCANT="$listCANT $N" ;;
esac
elif [ $rc -ne 0 ]; then
$PRINTF "${FAILED}:\n"
echo "$CMD" >&2
cat "$te" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
elif ! echo "$da" |diff - "$to" >$tdiff; then
$PRINTF "${FAILED}\n"
echo "$CMD" >&2
cat "$te" >&2
cat "$tdiff" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
else
$PRINTF "$OK\n"
numOK=$((numOK+1))
fi # command ok
fi ;; # NUMCOND, feats
esac
N=$((N+1))
##################################################################################
#=================================================================================
# here come tests that might affect your systems integrity. Put normal tests