Output statistics per option and SIGUSR1

This commit is contained in:
Gerhard Rieger 2023-10-26 18:42:41 +02:00
parent 2af6089436
commit c2196d6f15
17 changed files with 304 additions and 26 deletions

View file

@ -41,6 +41,10 @@ Features:
references. references.
Test: VARS_IN_SNIFFPATH Test: VARS_IN_SNIFFPATH
Socat option --statistics logs final byte and packet counter values
before exit. Signal USR1 logs actual values.
Tests: OPTION_STATISTICS SIGUSR1_STATISTICS
Corrections: Corrections:
When a sub process (EXEC, SYSTEM) terminated with exit code other than When a sub process (EXEC, SYSTEM) terminated with exit code other than
0, its last sent data might have been lost depending on timing of read/ 0, its last sent data might have been lost depending on timing of read/

View file

@ -76,7 +76,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes.
DOCFILES = README README.FIPS CHANGES FILES EXAMPLES PORTING SECURITY DEVELOPMENT doc/socat.yo doc/socat.1 doc/socat.html FAQ BUGREPORTS COPYING COPYING.OpenSSL doc/dest-unreach.css doc/socat-openssltunnel.html doc/socat-multicast.html doc/socat-tun.html doc/socat-genericsocket.html DOCFILES = README README.FIPS CHANGES FILES EXAMPLES PORTING SECURITY DEVELOPMENT doc/socat.yo doc/socat.1 doc/socat.html FAQ BUGREPORTS COPYING COPYING.OpenSSL doc/dest-unreach.css doc/socat-openssltunnel.html doc/socat-multicast.html doc/socat-tun.html doc/socat-genericsocket.html
SHFILES = daemon.sh mail.sh ftp.sh readline.sh \ SHFILES = daemon.sh mail.sh ftp.sh readline.sh \
socat_buildscript_for_android.sh socat_buildscript_for_android.sh
TESTFILES = test.sh socks4echo.sh proxyecho.sh gatherinfo.sh readline-test.sh \ TESTFILES = test.sh socks4echo.sh proxyecho.sh readline-test.sh \
proxy.sh socks4a-echo.sh proxy.sh socks4a-echo.sh
all: progs doc all: progs doc

View file

@ -672,6 +672,7 @@
#undef HAVE_GETGROUPLIST #undef HAVE_GETGROUPLIST
#undef WITH_HELP #undef WITH_HELP
#undef WITH_STATS
#undef WITH_STDIO #undef WITH_STDIO
#undef WITH_FDNUM #undef WITH_FDNUM
#undef WITH_FILE #undef WITH_FILE

View file

@ -163,6 +163,14 @@ AC_ARG_ENABLE(help, [ --disable-help disable help],
esac], esac],
[AC_DEFINE(WITH_HELP) AC_MSG_RESULT(yes)]) [AC_DEFINE(WITH_HELP) AC_MSG_RESULT(yes)])
AC_MSG_CHECKING(whether to include transfer statistics)
AC_ARG_ENABLE(stats, [ --disable-stats disable stats],
[case "$enableval" in
no) AC_MSG_RESULT(no);;
*) AC_DEFINE(WITH_STATS) AC_MSG_RESULT(yes);;
esac],
[AC_DEFINE(WITH_STATS) AC_MSG_RESULT(yes)])
AC_MSG_CHECKING(whether to include STDIO support) AC_MSG_CHECKING(whether to include STDIO support)
AC_ARG_ENABLE(stdio, [ --disable-stdio disable STDIO support], AC_ARG_ENABLE(stdio, [ --disable-stdio disable STDIO support],
[case "$enableval" in [case "$enableval" in

View file

@ -223,6 +223,11 @@ label(option_4)dit(bf(tt(-4)))
label(option_6)dit(bf(tt(-6))) label(option_6)dit(bf(tt(-6)))
Use IP version 6 in case that the addresses do not implicitly or explicitly Use IP version 6 in case that the addresses do not implicitly or explicitly
specify a version. specify a version.
label(option_statistics)dit(bf(tt(--statistics)))
Logs transfer statistics (bytes and blocks counters for both directions)
before terminating Socat.nl()
See also link(signal USR1)(signal_usr1).nl()
This feature is experimental and might change in future versions.
enddit() enddit()
@ -4057,6 +4062,16 @@ manpagefiles()
/usr/bin/procan /usr/bin/procan
label(SIGNALS)
manpagesection(SIGNALS)
description(
label(signal_usr1)dit(SIGUSR1:) Causes logging of current transfer statistics.
nl()
See also link(option --statistics)(option_statistics)
)
label(ENVIRONMENT_VARIABLES) label(ENVIRONMENT_VARIABLES)
manpagesection(ENVIRONMENT VARIABLES) manpagesection(ENVIRONMENT VARIABLES)

View file

@ -157,6 +157,9 @@ int procan(FILE *outfile) {
#endif #endif
#ifdef SIZE_MAX #ifdef SIZE_MAX
fprintf(outfile, "SIZE_MAX = %-24lu\n", SIZE_MAX); fprintf(outfile, "SIZE_MAX = %-24lu\n", SIZE_MAX);
#endif
#ifdef PIPE_BUF
fprintf(outfile, "PIPE_BUF = %-24d\n", PIPE_BUF);
#endif #endif
} }

104
socat.c
View file

@ -38,6 +38,7 @@ struct {
bool righttoleft; /* first addr wo, second addr ro */ bool righttoleft; /* first addr wo, second addr ro */
xiolock_t lock; /* a lock file */ xiolock_t lock; /* a lock file */
unsigned long log_sigs; /* signals to be caught just for logging */ unsigned long log_sigs; /* signals to be caught just for logging */
bool statistics; /* log statistics on exit */
} socat_opts = { } socat_opts = {
8192, /* bufsiz */ 8192, /* bufsiz */
false, /* verbose */ false, /* verbose */
@ -52,6 +53,7 @@ struct {
false, /* righttoleft */ false, /* righttoleft */
{ NULL, 0 }, /* lock */ { NULL, 0 }, /* lock */
1<<SIGHUP | 1<<SIGINT | 1<<SIGQUIT | 1<<SIGILL | 1<<SIGABRT | 1<<SIGBUS | 1<<SIGFPE | 1<<SIGSEGV | 1<<SIGTERM, /* log_sigs */ 1<<SIGHUP | 1<<SIGINT | 1<<SIGQUIT | 1<<SIGILL | 1<<SIGABRT | 1<<SIGBUS | 1<<SIGFPE | 1<<SIGSEGV | 1<<SIGTERM, /* log_sigs */
false /* statistics */
}; };
void socat_usage(FILE *fd); void socat_usage(FILE *fd);
@ -61,6 +63,7 @@ int socat(const char *address1, const char *address2);
int _socat(void); 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); void socat_signal(int sig);
void socat_signal_logstats(int sig);
static int socat_sigchild(struct single *file); static int socat_sigchild(struct single *file);
void lftocrlf(char **in, ssize_t *len, size_t bufsiz); void lftocrlf(char **in, ssize_t *len, size_t bufsiz);
@ -69,6 +72,7 @@ void crlftolf(char **in, ssize_t *len, size_t bufsiz);
static int socat_lock(void); static int socat_lock(void);
static void socat_unlock(void); static void socat_unlock(void);
static int socat_newchild(void); static int socat_newchild(void);
static void socat_print_stats(void);
static const char socatversion[] = static const char socatversion[] =
#include "./VERSION" #include "./VERSION"
@ -323,6 +327,8 @@ int main(int argc, const char *argv[]) {
case '-': case '-':
if (!strcmp("experimental", &arg1[0][2])) { if (!strcmp("experimental", &arg1[0][2])) {
xioparms.experimental = true; xioparms.experimental = true;
} else if (!strcmp("statistics", &arg1[0][2])) {
socat_opts.statistics = true;
} else { } else {
Error1("unknown option \"%s\"; use option \"-h\" for help", arg1[0]); Error1("unknown option \"%s\"; use option \"-h\" for help", arg1[0]);
} }
@ -374,7 +380,9 @@ int main(int argc, const char *argv[]) {
#endif /* WITH_MSGLEVEL <= E_DEBUG */ #endif /* WITH_MSGLEVEL <= E_DEBUG */
{ {
#if HAVE_SIGACTION
struct sigaction act; struct sigaction act;
#endif
int i, m; int i, m;
sigfillset(&act.sa_mask); /* while in sighandler block all signals */ sigfillset(&act.sa_mask); /* while in sighandler block all signals */
@ -383,9 +391,20 @@ int main(int argc, const char *argv[]) {
/* not sure which signals should be caught and print a message */ /* not sure which signals should be caught and print a message */
for (i = 0, m = 1; i < 8*sizeof(unsigned long); ++i, m <<= 1) { for (i = 0, m = 1; i < 8*sizeof(unsigned long); ++i, m <<= 1) {
if (socat_opts.log_sigs & m) { if (socat_opts.log_sigs & m) {
#if HAVE_SIGACTION
Sigaction(i, &act, NULL); Sigaction(i, &act, NULL);
#else
Signal(i, socat_signal);
#endif
} }
} }
#if HAVE_SIGACTION
act.sa_handler = socat_signal_logstats;
Sigaction(SIGUSR1, &act, NULL);
#else
Signal(SIGUSR1, socat_signal_logstats);
#endif
} }
Signal(SIGPIPE, SIG_IGN); Signal(SIGPIPE, SIG_IGN);
@ -400,6 +419,9 @@ int main(int argc, const char *argv[]) {
} }
Atexit(socat_unlock); Atexit(socat_unlock);
if (socat_opts.statistics) {
Atexit(socat_print_stats);
}
result = socat(arg1[0], arg1[1]); result = socat(arg1[0], arg1[1]);
if (result == EXIT_SUCCESS && engine_result != EXIT_SUCCESS) { if (result == EXIT_SUCCESS && engine_result != EXIT_SUCCESS) {
@ -428,6 +450,7 @@ void socat_usage(FILE *fd) {
fputs(" -D analyze file descriptors before loop\n", fd); fputs(" -D analyze file descriptors before loop\n", fd);
#endif #endif
fputs(" --experimental enable experimental features\n", fd); fputs(" --experimental enable experimental features\n", fd);
fputs(" --statistics output transfer statistics on exit\n", fd);
fputs(" -ly[facility] log to syslog, using facility (default is daemon)\n", fd); fputs(" -ly[facility] log to syslog, using facility (default is daemon)\n", fd);
fputs(" -lf<logfile> log to file\n", fd); fputs(" -lf<logfile> log to file\n", fd);
fputs(" -ls log to stderr (default if no other log)\n", fd); fputs(" -ls log to stderr (default if no other log)\n", fd);
@ -472,6 +495,16 @@ void socat_version(FILE *fd) {
fprintf(fd, " running on %s version %s, release %s, machine %s\n", fprintf(fd, " running on %s version %s, release %s, machine %s\n",
ubuf.sysname, ubuf.version, ubuf.release, ubuf.machine); ubuf.sysname, ubuf.version, ubuf.release, ubuf.machine);
fputs("features:\n", fd); fputs("features:\n", fd);
#ifdef WITH_HELP
fprintf(fd, " #define WITH_HELP %d\n", WITH_HELP);
#else
fputs(" #undef WITH_HELP\n", fd);
#endif
#ifdef WITH_STATS
fprintf(fd, " #define WITH_STATS %d\n", WITH_STATS);
#else
fputs(" #undef WITH_STATS\n", fd);
#endif
#ifdef WITH_STDIO #ifdef WITH_STDIO
fprintf(fd, " #define WITH_STDIO %d\n", WITH_STDIO); fprintf(fd, " #define WITH_STDIO %d\n", WITH_STDIO);
#else #else
@ -1339,6 +1372,10 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
} }
if (bytes > 0) { if (bytes > 0) {
#if WITH_STATS
++XIO_RDSTREAM(inpipe)->blocks_read;
XIO_RDSTREAM(inpipe)->bytes_read += bytes;
#endif
/* handle escape char */ /* handle escape char */
if (XIO_RDSTREAM(inpipe)->escape != -1) { if (XIO_RDSTREAM(inpipe)->escape != -1) {
/* check input data for escape char */ /* check input data for escape char */
@ -1490,6 +1527,10 @@ int xiotransfer(xiofile_t *inpipe, xiofile_t *outpipe,
} else { } else {
Info3("transferred "F_Zu" bytes from %d to %d", Info3("transferred "F_Zu" bytes from %d to %d",
writt, XIO_GETRDFD(inpipe), XIO_GETWRFD(outpipe)); writt, XIO_GETRDFD(inpipe), XIO_GETWRFD(outpipe));
#if WITH_STATS
++XIO_WRSTREAM(outpipe)->blocks_written;
XIO_WRSTREAM(outpipe)->bytes_written += writt;
#endif
} }
} }
return writt; return writt;
@ -1671,3 +1712,66 @@ static int socat_newchild(void) {
havelock = false; havelock = false;
return 0; return 0;
} }
#if WITH_STATS
void socat_signal_logstats(int signum) {
diag_in_handler = 1;
Notice1("socat_signal_logstats(): handling signal %d", signum);
socat_print_stats();
Notice1("socat_signal_logstats(): finishing signal %d", signum);
diag_in_handler = 0;
}
#endif /* WITH_STATS */
#if WITH_STATS
static void socat_print_stats(void)
{
const char ltorf0[] = "STATISTICS: left to right: %%%ullu packets(s), %%%ullu byte(s)";
const char rtolf0[] = "STATISTICS: right to left: %%%ullu packets(s), %%%ullu byte(s)";
char ltorf1[sizeof(ltorf0)]; /* final printf format with lengths of number */
char rtolf1[sizeof(rtolf0)]; /* final printf format with lengths of number */
unsigned int blocksd = 1, bytesd = 1; /* number of digits in output */
struct single *sock1w, *sock2w;
int savelevel;
if (sock1 == NULL || sock2 == NULL) {
Warn("transfer engine not yet started, statistics not available");
return;
}
if ((sock1->tag & ~XIO_TAG_CLOSED) == XIO_TAG_DUAL) {
sock1w = sock1->dual.stream[1];
} else {
sock1w = &sock1->stream;
}
if ((sock2->tag & ~XIO_TAG_CLOSED) == XIO_TAG_DUAL) {
sock2w = sock2->dual.stream[1];
} else {
sock2w = &sock2->stream;
}
if (!socat_opts.righttoleft && !socat_opts.righttoleft) {
/* Both directions - format output */
unsigned long long int maxblocks =
Max(sock1w->blocks_written, sock2w->blocks_written);
unsigned long long int maxbytes =
Max(sock1w->bytes_written, sock2w->bytes_written);
/* Calculate number of digits */
while (maxblocks >= 10) { ++blocksd; maxblocks /= 10; }
while (maxbytes >= 10) { ++bytesd; maxbytes /= 10; }
}
snprintf(ltorf1, sizeof(ltorf1), ltorf0, blocksd, bytesd);
snprintf(rtolf1, sizeof(rtolf1), rtolf0, blocksd, bytesd);
/* Statistics are E_INFO level; make sure they are printed anyway */
savelevel = diag_get_int('d');
diag_set_int('d', E_INFO);
Warn("statistics are experimental");
if (!socat_opts.righttoleft) {
Info2(ltorf1, sock2w->blocks_written, sock2w->bytes_written);
}
if (!socat_opts.lefttoright) {
Info2(rtolf1, sock1w->blocks_written, sock1w->bytes_written);
}
diag_set_int('d', savelevel);
return;
}
#endif /* WITH_STATs */

143
test.sh
View file

@ -278,7 +278,7 @@ tolower () {
psleep () { psleep () {
local T="$1" local T="$1"
[ "$T" = 0 ] && T=0.000002 [ "$T" = 0 ] && T=0.000002
$SOCAT -T "$T" pipe pipe $SOCAT -T "$T" pipe pipe 2>/dev/null
} }
# time in microseconds to wait in some situations # time in microseconds to wait in some situations
if ! type usleep >/dev/null 2>&1 || if ! type usleep >/dev/null 2>&1 ||
@ -289,7 +289,7 @@ if ! type usleep >/dev/null 2>&1 ||
*???????) S="${n%??????}"; uS="${n:${#n}-6}" ;; *???????) S="${n%??????}"; uS="${n:${#n}-6}" ;;
*) S=0; uS="00000$n"; uS="${uS:${#uS}-6}" ;; *) S=0; uS="00000$n"; uS="${uS:${#uS}-6}" ;;
esac esac
$SOCAT -T "$S.$uS" pipe pipe $SOCAT -T "$S.$uS" pipe pipe 2>/dev/null
} }
fi fi
#USLEEP=usleep #USLEEP=usleep
@ -974,6 +974,12 @@ runssctp6 () {
return 0; return 0;
} }
# check if UNIX domain sockets work
runsunix () {
# for now...
return 0;
}
# wait until an IP4 protocol is ready # wait until an IP4 protocol is ready
waitip4proto () { waitip4proto () {
local proto="$1" local proto="$1"
@ -13399,7 +13405,7 @@ N=$((N+1))
# Test the OpenSSL SNI feature # Test the OpenSSL SNI feature
NAME=OPENSSL_SNI NAME=OPENSSL_SNI
case "$TESTS" in case "$TESTS" in
*%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%$NAME%*) *%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%foreign%*|*%$NAME%*)
TEST="$NAME: Test the OpenSSL SNI feature" TEST="$NAME: Test the OpenSSL SNI feature"
# Connect to a server that is known to use SNI. Use an SNI name, not the # Connect to a server that is known to use SNI. Use an SNI name, not the
# certifications default name. When the TLS connection is established # certifications default name. When the TLS connection is established
@ -13446,7 +13452,7 @@ N=$((N+1))
# Test the openssl-no-sni option # Test the openssl-no-sni option
NAME=OPENSSL_NO_SNI NAME=OPENSSL_NO_SNI
case "$TESTS" in case "$TESTS" in
*%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%$NAME%*) *%$N%*|*%functions%*|*%socket%*|*%openssl%*|*%foreign%*|*%$NAME%*)
TEST="$NAME: Test the openssl-no-sni option" TEST="$NAME: Test the openssl-no-sni option"
# Connect to a server that is known to use SNI. Use an SNI name, not the # Connect to a server that is known to use SNI. Use an SNI name, not the
# certifications default name, and use option openssl-no-sni. # certifications default name, and use option openssl-no-sni.
@ -14498,7 +14504,7 @@ CMD="$TRACE $SOCAT $opts -d -d -d -d /dev/null UDP4-SENDTO:$LOCALHOST:$PORT,lowp
printf "test $F_n $TEST... " $N printf "test $F_n $TEST... " $N
$CMD >/dev/null 2>"${te}" $CMD >/dev/null 2>"${te}"
rc1=$? rc1=$?
LOWPORT=$(grep 'D bind(.*:' $te |sed 's/.*:\([0-9][0-9]*\),.*/\1/') LOWPORT=$(grep '[DE] bind(.*:' $te |sed 's/.*:\([0-9][0-9]*\),.*/\1/')
#echo "LOWPORT=\"$LOWPORT\"" >&2 #echo "LOWPORT=\"$LOWPORT\"" >&2
#type socat >&2 #type socat >&2
if [[ $LOWPORT =~ [0-9][0-9]* ]] && [ "$LOWPORT" -ge 640 -a "$LOWPORT" -le 1023 ]; then if [[ $LOWPORT =~ [0-9][0-9]* ]] && [ "$LOWPORT" -ge 640 -a "$LOWPORT" -le 1023 ]; then
@ -14966,7 +14972,6 @@ N=$((N+1))
while read KEYW FEAT RUNS ADDR IPPORT; do while read KEYW FEAT RUNS ADDR IPPORT; do
if [ -z "$KEYW" ] || [[ "$KEYW" == \#* ]]; then continue; fi if [ -z "$KEYW" ] || [[ "$KEYW" == \#* ]]; then continue; fi
RUNS=$(tolower $KEYW)
PROTO=$KEYW PROTO=$KEYW
proto="$(echo "$PROTO" |tr A-Z a-z)" proto="$(echo "$PROTO" |tr A-Z a-z)"
feat="$(tolower "$FEAT")" feat="$(tolower "$FEAT")"
@ -15054,8 +15059,8 @@ fi # NUMCOND
esac esac
N=$((N+1)) N=$((N+1))
done <<<" done <<<"
UDP4 UDP udp4 127.0.0.1 PORT UDP4 UDP ip4 127.0.0.1 PORT
UDP6 UDP udp4 [::1] PORT UDP6 UDP ip6 [::1] PORT
UNIX unix unix $td/test\$N.server - UNIX unix unix $td/test\$N.server -
" "
@ -15395,6 +15400,127 @@ esac
N=$((N+1)) N=$((N+1))
# Test logging of statistics on Socat option --statistics
NAME=OPTION_STATISTICS
case "$TESTS" in
*%$N%*|*%functions%*|*%stats%*|*%system%*|*%stdio%*|*%$NAME%*)
TEST="$NAME: Socat option --statistics"
# Invoke Socat with option --statistics, transfer some date, and check the log
# file for the values
if ! eval $NUMCOND; then :;
elif ! $(type >/dev/null 2>&1); then
$PRINTF "test $F_n $TEST... ${YELLOW}tee not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! F=$(testfeats STATS STDIO SYSTEM); then
$PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! A=$(testaddrs STDIO SYSTEM); then
$PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! o=$(testoptions pty cfmakeraw) >/dev/null; then
$PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
else
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
CMD0="$TRACE $SOCAT $opts --statistics STDIO SYSTEM:'tee /dev/stdout',pty,cfmakeraw"
printf "test $F_n $TEST... " $N
echo "$da" |eval "$CMD0" >"${tf}0" 2>"${te}0"
rc0=$?
if [ $rc0 -ne 0 ]; then
# The test could not run meaningfully
$PRINTF "$CANT\n"
if [ "$VERBOSE" ]; then echo "$CMD0"; fi
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif [ $(grep STATISTICS "${te}0" |wc -l) -eq 2 ]; then
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD0"; fi
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi
numOK=$((numOK+1))
else
$PRINTF "$FAILED\n"
echo "$CMD0 &"
cat "${te}0" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
namesFAIL="$namesFAIL $NAME"
fi
fi # NUMCOND
;;
esac
N=$((N+1))
# Test logging of statistics on SIGUSR1
NAME=SIGUSR1_STATISTICS
case "$TESTS" in
*%$N%*|*%functions%*|*%signal%*|*%stats%*|*%system%*|*%stdio%*|*%$NAME%*)
TEST="$NAME: statistics on SIGUSR1"
# Invoke Socat without option --statistics, transfer some date, send signal
# USR1,and check the log file for the values
if ! eval $NUMCOND; then :;
elif ! $(type tee >/dev/null 2>&1); then
$PRINTF "test $F_n $TEST... ${YELLOW}tee not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! $(type pkill >/dev/null 2>&1); then
$PRINTF "test $F_n $TEST... ${YELLOW}pkill not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! F=$(testfeats STATS STDIO SYSTEM); then
$PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available in $SOCAT${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! A=$(testaddrs STDIO SYSTEM); then
$PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
elif ! o=$(testoptions pty cfmakeraw) >/dev/null; then
$PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N
numCANT=$((numCANT+1))
listCANT="$listCANT $N"
else
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
da="test$N $(date) $RANDOM"
CMD0="$TRACE $SOCAT $opts STDIO SYSTEM:'tee /dev/stdout 2>/dev/null',pty,cfmakeraw"
#set -vx
printf "test $F_n $TEST... " $N
{ echo "$da"; relsleep 3; } |eval "$CMD0" >"${tf}0" 2>"${te}0" &
pid0=$!
relsleep 2
TTY=$(tty |sed 's|/dev/||')
pkill -USR1 -t $TTY socat || { echo "pkill -t $TTY -USR1 socat"; }
relsleep 1
pkill -t $TTY socat
wait
if [ "$(grep STATISTICS "${te}0" |wc -l)" -eq 2 ]; then
$PRINTF "$OK\n"
if [ "$VERBOSE" ]; then echo "$CMD0"; fi
if [ "$DEBUG" ]; then cat "${te}0" >&2; fi
numOK=$((numOK+1))
else
$PRINTF "$FAILED\n"
echo "$CMD0 &"
cat "${te}0" >&2
numFAIL=$((numFAIL+1))
listFAIL="$listFAIL $N"
namesFAIL="$namesFAIL $NAME"
fi
fi # NUMCOND
;;
esac
N=$((N+1))
# end of common tests # end of common tests
################################################################################## ##################################################################################
@ -15528,6 +15654,7 @@ exit
NAME=SHORT_UNIQUE_TESTNAME NAME=SHORT_UNIQUE_TESTNAME
case "$TESTS" in case "$TESTS" in
*%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%$NAME%*) *%$N%*|*%functions%*|*%bugs%*|*%socket%*|*%$NAME%*)
#*%foreign%*|*%root%*|*%listen%*|*%fork%*|*%ip4%*|*%tcp4%*|*%bug%*|...
TEST="$NAME: give a one line description of test" TEST="$NAME: give a one line description of test"
# Describe how the test is performed, and what's the success criteria # Describe how the test is performed, and what's the success criteria
if ! eval $NUMCOND; then :; if ! eval $NUMCOND; then :;

View file

@ -197,7 +197,10 @@ int _xioopen_open(const char *path, int rw, struct opt *opts) {
retropt_modet(opts, OPT_PERM, &mode); retropt_modet(opts, OPT_PERM, &mode);
if ((fd = Open(path, flags, mode)) < 0) { do {
fd = Open(path, flags, mode);
} while (fd < 0 && errno == EINTR);
if (fd < 0) {
Error4("open(\"%s\", 0%lo, 0%03o): %s", Error4("open(\"%s\", 0%lo, 0%03o): %s",
path, flags, mode, strerror(errno)); path, flags, mode, strerror(errno));
return STAT_RETRYLATER; return STAT_RETRYLATER;

View file

@ -153,7 +153,7 @@ static int xioopen_fifo(int argc, const char *argv[], struct opt *opts, int xiof
} }
} else { } else {
/* exists */ /* exists */
Debug1("xioopen_fifo(\"%s\"): already exist, opening it", pipename); Info1("xioopen_fifo(\"%s\"): already exist, opening it", pipename);
Notice3("opening %s \"%s\" for %s", Notice3("opening %s \"%s\" for %s",
filetypenames[(pipstat.st_mode&S_IFMT)>>12], filetypenames[(pipstat.st_mode&S_IFMT)>>12],
pipename, ddirection[rw]); pipename, ddirection[rw]);

9
xio.h
View file

@ -85,8 +85,9 @@ enum xiotag {
XIO_TAG_RDONLY, /* this is a single read-only stream */ XIO_TAG_RDONLY, /* this is a single read-only stream */
XIO_TAG_WRONLY, /* this is a single write-only stream */ XIO_TAG_WRONLY, /* this is a single write-only stream */
XIO_TAG_RDWR, /* this is a single read-write stream */ XIO_TAG_RDWR, /* this is a single read-write stream */
XIO_TAG_DUAL /* this is a dual stream, consisting of two single XIO_TAG_DUAL, /* this is a dual stream, consisting of two single
streams */ streams */
XIO_TAG_CLOSED=8, /* close, additional bit */
} ; } ;
/* Keep condition consistent with xioopts.h:GROUP_*! */ /* Keep condition consistent with xioopts.h:GROUP_*! */
@ -278,6 +279,12 @@ typedef struct single {
} tun; } tun;
#endif /* WITH_TUN */ #endif /* WITH_TUN */
} para; } para;
#if WITH_STATS
unsigned long long blocks_read;
unsigned long long bytes_read;
unsigned long long blocks_written;
unsigned long long bytes_written;
#endif /* WITH_STATS */
} xiosingle_t; } xiosingle_t;
/* rw: 0..read, 1..write, 2..r/w */ /* rw: 0..read, 1..write, 2..r/w */

View file

@ -93,7 +93,7 @@ int xioclose1(struct single *pipe) {
free(pipe->unlink_close); free(pipe->unlink_close);
} }
pipe->tag = XIO_TAG_INVALID; pipe->tag |= XIO_TAG_CLOSED;
return 0; /*! */ return 0; /*! */
} }
@ -111,7 +111,7 @@ int xioclose(xiofile_t *file) {
if (file->tag == XIO_TAG_DUAL) { if (file->tag == XIO_TAG_DUAL) {
result = xioclose1(file->dual.stream[0]); result = xioclose1(file->dual.stream[0]);
result |= xioclose1(file->dual.stream[1]); result |= xioclose1(file->dual.stream[1]);
file->tag = XIO_TAG_INVALID; file->tag |= XIO_TAG_CLOSED;
} else { } else {
result = xioclose1(&file->stream); result = xioclose1(&file->stream);
} }

View file

@ -18,7 +18,8 @@ void xioexit(void) {
diag_in_handler = 0; diag_in_handler = 0;
Debug("starting xioexit()"); Debug("starting xioexit()");
for (i = 0; i < XIO_MAXSOCK; ++i) { for (i = 0; i < XIO_MAXSOCK; ++i) {
if (sock[i] != NULL && sock[i]->tag != XIO_TAG_INVALID) { if (sock[i] != NULL && sock[i]->tag != XIO_TAG_INVALID &&
!(sock[i]->tag & XIO_TAG_CLOSED)) {
xioclose(sock[i]); xioclose(sock[i]);
} }
} }

View file

@ -121,22 +121,27 @@ void xiodroplocks(void) {
int i; int i;
for (i = 0; i < XIO_MAXSOCK; ++i) { for (i = 0; i < XIO_MAXSOCK; ++i) {
if (sock[i] != NULL && sock[i]->tag != XIO_TAG_INVALID) { if (sock[i] != NULL && sock[i]->tag != XIO_TAG_INVALID &&
!(sock[i]->tag & XIO_TAG_CLOSED)) {
xiofiledroplock(sock[i]); xiofiledroplock(sock[i]);
} }
} }
} }
/* consider an invokation like this: /* Consider an invocation like this:
socat -u exec:'some program that accepts data' tcp-l:...,fork socat -u EXEC:'some program that accepts data' TCP-L:...,fork
we do not want the program to be killed by the first tcp-l sub process, it's we do not want the program to be killed by the first TCP-L sub process, it's
better if it survives all sub processes. Thus, it must not be killed when better if it survives all sub processes. Thus, it must not be killed when
the sub process delivers EOF. Also, a socket that is reused in sub processes the sub process delivers EOF. Also, a socket that is reused in sub processes
should not be shut down (affects the connection), but closed (affects only should not be shut down (affects the connection), but closed (affects only
sub processes copy of file descriptor) */ sub processes copy of file descriptor) */
static int xio_nokill(xiofile_t *sock) { static int xio_nokill(xiofile_t *sock) {
int result = 0; int result = 0;
if (sock->tag & XIO_TAG_CLOSED) {
return -1;
}
switch (sock->tag) { switch (sock->tag) {
case XIO_TAG_INVALID: case XIO_TAG_INVALID:
default: default:

View file

@ -23,7 +23,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
struct single *pipe; struct single *pipe;
int _errno; int _errno;
if (file->tag == XIO_TAG_INVALID) { if (file->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xioread(): invalid xiofile descriptor %p", file); Error1("xioread(): invalid xiofile descriptor %p", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;
@ -31,7 +31,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
if (file->tag == XIO_TAG_DUAL) { if (file->tag == XIO_TAG_DUAL) {
pipe = file->dual.stream[0]; pipe = file->dual.stream[0];
if (pipe->tag == XIO_TAG_INVALID) { if (pipe->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xioread(): invalid xiofile sub descriptor %p[0]", file); Error1("xioread(): invalid xiofile sub descriptor %p[0]", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;
@ -433,7 +433,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) {
ssize_t xiopending(xiofile_t *file) { ssize_t xiopending(xiofile_t *file) {
struct single *pipe; struct single *pipe;
if (file->tag == XIO_TAG_INVALID) { if (file->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xiopending(): invalid xiofile descriptor %p", file); Error1("xiopending(): invalid xiofile descriptor %p", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;
@ -441,7 +441,7 @@ ssize_t xiopending(xiofile_t *file) {
if (file->tag == XIO_TAG_DUAL) { if (file->tag == XIO_TAG_DUAL) {
pipe = file->dual.stream[0]; pipe = file->dual.stream[0];
if (pipe->tag == XIO_TAG_INVALID) { if (pipe->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xiopending(): invalid xiofile sub descriptor %p[0]", file); Error1("xiopending(): invalid xiofile sub descriptor %p[0]", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;

View file

@ -25,7 +25,7 @@ static void signal_kill_pid(int dummy) {
int xioshutdown(xiofile_t *sock, int how) { int xioshutdown(xiofile_t *sock, int how) {
int result = 0; int result = 0;
if (sock->tag == XIO_TAG_INVALID) { if (sock->tag == XIO_TAG_INVALID || sock->tag & XIO_TAG_CLOSED) {
Error("xioshutdown(): invalid file descriptor"); Error("xioshutdown(): invalid file descriptor");
errno = EINVAL; errno = EINVAL;
return -1; return -1;

View file

@ -22,7 +22,7 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
struct single *pipe; struct single *pipe;
int _errno; int _errno;
if (file->tag == XIO_TAG_INVALID) { if (file->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xiowrite(): invalid xiofile descriptor %p", file); Error1("xiowrite(): invalid xiofile descriptor %p", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;
@ -30,7 +30,7 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) {
if (file->tag == XIO_TAG_DUAL) { if (file->tag == XIO_TAG_DUAL) {
pipe = file->dual.stream[1]; pipe = file->dual.stream[1];
if (pipe->tag == XIO_TAG_INVALID) { if (pipe->tag == XIO_TAG_INVALID || file->tag & XIO_TAG_CLOSED) {
Error1("xiowrite(): invalid xiofile sub descriptor %p[1]", file); Error1("xiowrite(): invalid xiofile sub descriptor %p[1]", file);
errno = EINVAL; errno = EINVAL;
return -1; return -1;