From c311542e118308d0b1ec259c91178e3413962477 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Sat, 30 Sep 2023 18:30:56 +0200 Subject: [PATCH] New address SOCKETPAIR for echoing datagrams --- CHANGES | 6 +++ Makefile.in | 4 +- config.h.in | 1 + configure.ac | 8 ++++ doc/socat.yo | 15 +++++-- socat.c | 5 +++ test.sh | 102 +++++++++++++++++++++++++++++++++++++++++++++++ xio-pipe.c | 3 ++ xio-pipe.h | 2 - xio-socket.c | 1 - xio-socketpair.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++ xio-socketpair.h | 10 +++++ xio.h | 4 +- xiomodes.h | 1 + xioopen.c | 3 ++ xioopts.h | 1 - xioread.c | 24 ++++++++++- xiowrite.c | 6 ++- 18 files changed, 285 insertions(+), 13 deletions(-) create mode 100644 xio-socketpair.c create mode 100644 xio-socketpair.h diff --git a/CHANGES b/CHANGES index 10e5c00..3996481 100644 --- a/CHANGES +++ b/CHANGES @@ -13,6 +13,12 @@ Features: The number of warnings has been reduced, e.g.removing a non existing file does in most cases no longer log a warning. + Added address type internal SOCKETPAIR. This is similar to the unnamed + PIPE address (only for internal echoing) but it provides datagram mode + (the default) and thus keeps packet boundaries. + Tests: SOCKETPAIR_STREAM SOCKETPAIR_DATAGRAM SOCKETPAIR_SEQPACKET + SOCKETPAIR_BOUNDARIES + New option -S controls catching and logging of signals that are not internally used by Socat. Tests: SIGTERM_NOLOG SIG31_LOG diff --git a/Makefile.in b/Makefile.in index a54a954..95fcca9 100644 --- a/Makefile.in +++ b/Makefile.in @@ -45,7 +45,7 @@ XIOSRCS = xioinitialize.c xiohelp.c xioparam.c xiodiag.c xioopen.c xioopts.c \ xiosignal.c xiosigchld.c xioread.c xiowrite.c \ xiolayer.c xioshutdown.c xioclose.c xioexit.c \ xio-process.c xio-fd.c xio-fdnum.c xio-stdio.c xio-pipe.c \ - xio-gopen.c xio-creat.c xio-file.c xio-named.c \ + xio-socketpair.c xio-gopen.c xio-creat.c xio-file.c xio-named.c \ xio-socket.c xio-interface.c xio-listen.c xio-unix.c xio-vsock.c \ xio-ip.c xio-ip4.c xio-ip6.c xio-ipapp.c xio-tcp.c \ xio-sctp.c xio-rawip.c \ @@ -64,7 +64,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes. xioconfig.h mytypes.h xioopts.h xiodiag.h xiohelp.h xiosysincludes.h \ xiomodes.h xiolayer.h xio-process.h xio-fd.h xio-fdnum.h xio-stdio.h \ xio-named.h xio-file.h xio-creat.h xio-gopen.h xio-pipe.h \ - xio-socket.h xio-interface.h xio-listen.h xio-unix.h xio-vsock.h \ + xio-socketpair.h xio-socket.h xio-interface.h xio-listen.h xio-unix.h xio-vsock.h \ xio-ip.h xio-ip4.h xio-ip6.h xio-rawip.h \ xio-ipapp.h xio-tcp.h xio-udp.h xio-sctp.h \ xio-socks.h xio-proxy.h xio-progcall.h xio-exec.h \ diff --git a/config.h.in b/config.h.in index a87bf21..67e566c 100644 --- a/config.h.in +++ b/config.h.in @@ -692,6 +692,7 @@ #undef WITH_GOPEN #undef WITH_TERMIOS #undef WITH_PIPE +#undef WITH_SOCKETPAIR #undef WITH_UNIX #undef WITH_ABSTRACT_UNIXSOCKET #undef WITH_IP4 diff --git a/configure.ac b/configure.ac index bf04260..7dced77 100644 --- a/configure.ac +++ b/configure.ac @@ -219,6 +219,14 @@ AC_ARG_ENABLE(pipe, [ --disable-pipe disable pipe support], esac], [AC_DEFINE(WITH_PIPE) AC_MSG_RESULT(yes)]) +AC_MSG_CHECKING(whether to include explicit socketpair support) +AC_ARG_ENABLE(socketpair, [ --disable-socketpair disable socketpair support], + [case "$enableval" in + no) AC_MSG_RESULT(no);; + *) AC_DEFINE(WITH_SOCKETPAIR) AC_MSG_RESULT(yes);; + esac], + [AC_DEFINE(WITH_SOCKETPAIR) AC_MSG_RESULT(yes)]) + AC_MSG_CHECKING(whether to include explicit termios support) AC_ARG_ENABLE(termios, [ --disable-termios disable termios support], [case "$enableval" in diff --git a/doc/socat.yo b/doc/socat.yo index b28e55f..114d82f 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -682,13 +682,22 @@ label(ADDRESS_NAMED_PIPE)dit(bf(tt(PIPE:))) See also: link(unnamed pipe)(ADDRESS_UNNAMED_PIPE) label(ADDRESS_UNNAMED_PIPE)dit(bf(tt(PIPE))) Creates an unnamed pipe and uses it for reading and writing. It works as an - echo, because everything written - to it appeares immediately as read data.nl() + echo, because everything written to it appeares immediately as read + data.nl() Note: When socat tries to write more bytes than the pipe can queue (Linux 2.4: 2048 bytes), socat might block. Consider, e.g., using option code(-b 2048) nl() Option groups: link(FD)(GROUP_FD) nl() - See also: link(named pipe)(ADDRESS_NAMED_PIPE) + See also: link(named pipe)(ADDRESS_NAMED_PIPE), link(SOCKETPAIR)(ADDRESS_SOCKETPAIR) +label(ADDRESS_SOCKETPAIR)dit(bf(tt(SOCKETPAIR))) + Creates a socketpair and uses it for reading and writing. It works as an + echo, because everything written to it appeares immediately as read + data. The default socket type is datagram, so it keeps packet boundaries. + nl() + Option groups: link(FD)(GROUP_FD) nl() + Useful options: + link(socktype)(OPTION_SO_TYPE)nl() + See also: link(unnamed pipe)(ADDRESS_UNNAMED_PIPE) label(ADDRESS_PROXY_CONNECT)dit(bf(tt(PROXY:::))) Connects to an HTTP proxy server on port 8080 using TCP/IP version 4 or 6 depending on address specification, name resolution, or option diff --git a/socat.c b/socat.c index 6f25707..e2b1bd8 100644 --- a/socat.c +++ b/socat.c @@ -540,6 +540,11 @@ void socat_version(FILE *fd) { #else fputs(" #undef WITH_PIPE\n", fd); #endif +#ifdef WITH_SOCKETPAIR + fprintf(fd, " #define WITH_SOCKETPAIR %d\n", WITH_SOCKETPAIR); +#else + fputs(" #undef WITH_SOCKETPAIR\n", fd); +#endif #ifdef WITH_UNIX fprintf(fd, " #define WITH_UNIX %d\n", WITH_UNIX); #else diff --git a/test.sh b/test.sh index 0fea81b..a52b78d 100755 --- a/test.sh +++ b/test.sh @@ -16546,6 +16546,108 @@ esac N=$((N+1)) +NAME=SOCKETPAIR_STREAM +case "$TESTS" in +*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*) +TEST="$NAME: stdio and internal socketpair with stream" +testecho "$N" "$TEST" "STDIO" "SOCKETPAIR" "$opts" +esac +N=$((N+1)) + +NAME=SOCKETPAIR_DATAGRAM +case "$TESTS" in +*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*) +TEST="$NAME: stdio and internal socketpair with datagram" +testecho "$N" "$TEST" "STDIO" "SOCKETPAIR,socktype=2" "$opts" +esac +N=$((N+1)) + +NAME=SOCKETPAIR_SEQPACKET +case "$TESTS" in +*%$N%*|*%functions%*|*%stdio%*|*%socketpair%*|*%$NAME%*) +TEST="$NAME: stdio and internal socketpair with seqpacket" +testecho "$N" "$TEST" "STDIO" "SOCKETPAIR,socktype=$SOCK_SEQPACKET" "$opts" +esac +N=$((N+1)) + +# Test if SOCKETPAIR address with SOCK_DGRAM keeps packet boundaries +NAME=SOCKETPAIR_BOUNDARIES +case "$TESTS" in +*%$N%*|*%functions%*|*%socketpair%*|*%udp%*|*%udp4%*|*%ip4%*|*%dgram%*|*%$NAME%*) +TEST="$NAME: Internal socketpair keeps packet boundaries" +# Start a UDP4-DATAGRAM process that echoes data with datagram SOCKETPAIR; +# a client sends two packets with 24 and ~18 bytes using a UDP4-DATAGRAM. The +# client truncates packets to size 24, so when a large merged packet comes from +# server some data will be lost. If the original data is received, the test +# succeeded. +if ! eval $NUMCOND; then :; +elif ! F=$(testfeats STDIO IP4 UDP SOCKETPAIR); 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 UDP4-DATAGRAM SOCKETPAIR); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions bind socktype ) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 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" +ts1p=$PORT; PORT=$((PORT+1)) +ts2p=$PORT; PORT=$((PORT+1)) +da="test$N $(date) $RANDOM" +CMD1="$TRACE $SOCAT $opts -T 0.2 UDP4-DATAGRAM:$LOCALHOST:$ts2p,bind=$LOCALHOST:$ts1p SOCKETPAIR,socktype=$SOCK_DGRAM" +CMD2="$TRACE $SOCAT $opts -b 24 -t 0.2 -T 0.3 - UDP4-DATAGRAM:$LOCALHOST:$ts1p,bind=$LOCALHOST:$ts2p" +printf "test $F_n $TEST... " $N +export SOCAT_TRANSFER_WAIT=0.2 +$CMD1 2>"${te}1" & +pid1="$!" +unset SOCAT_TRANSFER_WAIT +waitudp4port $ts1p 1 +{ echo -n "${da:0:20}"; usleep 100000; echo "${da:20}"; } |$CMD2 >>"$tf" 2>>"${te}2" +rc2="$?" +kill "$pid1" 2>/dev/null; wait; +if [ "$rc2" -ne 0 ]; then + $PRINTF "$FAILED (rc2=$rc2): $TRACE $SOCAT:\n" + echo "$CMD1 &" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +elif ! echo "$da" |diff - "$tf" >"$tdiff"; then + $PRINTF "$FAILED\n" + echo "$CMD1 &" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + echo diff: + cat "$tdiff" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" +else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD2 &"; fi + if [ "$DEBUG" ]; then cat "${te}2" >&2; fi + numOK=$((numOK+1)) +fi +fi # NUMCOND + ;; +esac +PORT=$((PORT+1)) +N=$((N+1)) + + # end of common tests ################################################################################## diff --git a/xio-pipe.c b/xio-pipe.c index c4c8ceb..5b7d077 100644 --- a/xio-pipe.c +++ b/xio-pipe.c @@ -9,6 +9,8 @@ #include "xio-named.h" +#include "xio-pipe.h" + #if WITH_PIPE @@ -40,6 +42,7 @@ static int xioopen_fifo_unnamed(xiofile_t *sock, struct opt *opts) { sock->stream.dtype = XIODATA_PIPE; sock->stream.fd = filedes[0]; sock->stream.para.bipipe.fdout = filedes[1]; + sock->stream.para.bipipe.socktype = SOCK_STREAM; /* due to socketpair reuse */ applyopts_cloexec(sock->stream.fd, opts); applyopts_cloexec(sock->stream.para.bipipe.fdout, opts); diff --git a/xio-pipe.h b/xio-pipe.h index 34d95fa..1ea8ba9 100644 --- a/xio-pipe.h +++ b/xio-pipe.h @@ -7,6 +7,4 @@ extern const struct addrdesc xioaddr_pipe; -extern int xioopen_fifo_unnamed(char *arg, xiofile_t *sock); - #endif /* !defined(__xio_pipe_h_included) */ diff --git a/xio-socket.c b/xio-socket.c index 81b827a..9af7104 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -202,7 +202,6 @@ const struct optdesc opt_siocspgrp = { "siocspgrp", NULL, OPT_SIOCSPGRP, GRO const struct optdesc opt_bind = { "bind", NULL, OPT_BIND, GROUP_SOCKET, PH_BIND, TYPE_STRING,OFUNC_SPEC }; const struct optdesc opt_connect_timeout = { "connect-timeout", NULL, OPT_CONNECT_TIMEOUT, GROUP_SOCKET, PH_PASTSOCKET, TYPE_TIMEVAL, OFUNC_OFFSET, XIO_OFFSETOF(para.socket.connect_timeout) }; const struct optdesc opt_protocol_family = { "protocol-family", "pf", OPT_PROTOCOL_FAMILY, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC }; -const struct optdesc opt_protocol = { "protocol", NULL, OPT_PROTOCOL, GROUP_SOCKET, PH_PRESOCKET, TYPE_STRING, OFUNC_SPEC }; /* generic setsockopt() options */ const struct optdesc opt_setsockopt = { "setsockopt", "sockopt", OPT_SETSOCKOPT_BIN, GROUP_SOCKET,PH_CONNECTED, TYPE_INT_INT_BIN, OFUNC_SOCKOPT_GENERIC, 0, 0 }; diff --git a/xio-socketpair.c b/xio-socketpair.c new file mode 100644 index 0000000..49396af --- /dev/null +++ b/xio-socketpair.c @@ -0,0 +1,102 @@ +/* source: xio-socketpair.c */ +/* Copyright Gerhard Rieger and contributors (see file CHANGES) */ +/* Published under the GNU General Public License V.2, see file COPYING */ + +/* this file contains the source for opening addresses of socketpair type */ + +#include "xiosysincludes.h" +#include "xioopen.h" + +#include "xio-named.h" + +#include "xio-socketpair.h" + + +#if WITH_SOCKETPAIR + +static int xioopen_socketpair(int argc, const char *argv[], struct opt *opts, int xioflags, xiofile_t *xfd, groups_t groups, int dummy1, int dummy2, int dummy3); + + +const struct addrdesc xioaddr_socketpair = { "SOCKETPAIR", 3, xioopen_socketpair, GROUP_FD|GROUP_SOCKET, 0, 0, 0 HELP(":") }; + + +/* open a socketpair */ +static int xioopen_socketpair( + int argc, + const char *argv[], + struct opt *opts, + int xioflags, + xiofile_t *xfd, + groups_t groups , + int dummy1, + int dummy2, + int dummy3) +{ + struct single *sfd; + struct opt *opts2; + int pf = PF_UNIX; + int protocol = 0; + int filedes[2]; + int numleft; + int result; + + if (argc != 1) { + Error2("%s: wrong number of parameters (%d instead of 1)", argv[0], argc-1); + } + + sfd = &xfd->stream; + sfd->para.bipipe.socktype = SOCK_DGRAM; + if (applyopts_single(sfd, opts, PH_INIT) < 0) return -1; + applyopts(-1, opts, PH_INIT); + retropt_int(opts, OPT_PROTOCOL_FAMILY, &pf); + retropt_int(opts, OPT_SO_TYPE, &sfd->para.bipipe.socktype); + retropt_int(opts, OPT_SO_PROTOTYPE, &protocol); + + if (Socketpair(pf, sfd->para.bipipe.socktype, protocol, filedes) != 0) { + Error5("socketpair(%d, %d, %d, %p): %s", pf, sfd->para.bipipe.socktype, protocol, filedes, strerror(errno)); + return -1; + } + Info2("socketpair({%d,%d})", filedes[0], filedes[1]); + + sfd->tag = XIO_TAG_RDWR; + if (sfd->para.bipipe.socktype == SOCK_STREAM) { + sfd->dtype = XIOREAD_STREAM|XIOWRITE_PIPE; + } else { + sfd->dtype = XIOREAD_RECV|XIOREAD_RECV_NOCHECK|XIOWRITE_PIPE; + } + sfd->fd = filedes[0]; + sfd->para.bipipe.fdout = filedes[1]; + applyopts_cloexec(sfd->fd, opts); + applyopts_cloexec(sfd->para.bipipe.fdout, opts); + + /* one-time and input-direction options, no second application */ + retropt_bool(opts, OPT_IGNOREEOF, &sfd->ignoreeof); + + /* here we copy opts! */ + if ((opts2 = copyopts(opts, GROUP_SOCKET)) == NULL) { + return STAT_NORETRY; + } + + /* apply options to first FD */ + if ((result = applyopts(sfd->fd, opts, PH_ALL)) < 0) { + return result; + } + if ((result = applyopts_single(sfd, opts, PH_ALL)) < 0) { + return result; + } + + /* apply options to second FD */ + if ((result = applyopts(sfd->para.bipipe.fdout, opts2, PH_ALL)) < 0) + { + return result; + } + + if ((numleft = leftopts(opts)) > 0) { + Error1("%d option(s) could not be used", numleft); + showleft(opts); + } + Notice("writing to and reading from unnamed socketpair"); + return 0; +} + +#endif /* WITH_SOCKETPAIR */ diff --git a/xio-socketpair.h b/xio-socketpair.h new file mode 100644 index 0000000..2f92f70 --- /dev/null +++ b/xio-socketpair.h @@ -0,0 +1,10 @@ +/* source: xio-socketpair.h */ +/* Copyright Gerhard Rieger and contributors (see file CHANGES) */ +/* Published under the GNU General Public License V.2, see file COPYING */ + +#ifndef __xio_socketpair_h_included +#define __xio_socketpair_h_included 1 + +const extern struct addrdesc xioaddr_socketpair; + +#endif /* !defined(__xio_socketpair_h_included) */ diff --git a/xio.h b/xio.h index 1d72fa3..6c3d057 100644 --- a/xio.h +++ b/xio.h @@ -61,6 +61,7 @@ struct opt; #define XIOREAD_RECV_ONESHOT 0x0008 /* give EOF after first packet */ #define XIOREAD_RECV_SKIPIP 0x0010 /* recv, skip IPv4 header */ #define XIOREAD_RECV_FROM 0x0020 /* remember peer for replying */ +#define XIOREAD_RECV_NOCHECK 0x0040 /* do not check peer */ /* combinations */ #define XIODATA_MASK (XIODATA_READMASK|XIODATA_WRITEMASK) @@ -211,6 +212,7 @@ typedef struct single { union { struct { int fdout; /* use fd for output */ + int socktype; } bipipe; #if _WITH_SOCKET struct { @@ -242,8 +244,8 @@ typedef struct single { } socket; #endif /* _WITH_SOCKET */ struct { - pid_t pid; /* child PID, with EXEC: */ int fdout; /* use fd for output if two pipes */ + pid_t pid; /* child PID, with EXEC: */ struct timeval sitout_eio; } exec; #if WITH_READLINE diff --git a/xiomodes.h b/xiomodes.h index 7a20547..8ab1b9a 100644 --- a/xiomodes.h +++ b/xiomodes.h @@ -15,6 +15,7 @@ #include "xio-creat.h" #include "xio-gopen.h" #include "xio-pipe.h" +#include "xio-socketpair.h" #if _WITH_SOCKET #include "xio-socket.h" #include "xio-listen.h" diff --git a/xioopen.c b/xioopen.c index 1a87ac8..3aeed05 100644 --- a/xioopen.c +++ b/xioopen.c @@ -193,6 +193,9 @@ const struct addrname addressnames[] = { { "SOCKET-RECVFROM", &xioaddr_socket_recvfrom }, { "SOCKET-SENDTO", &xioaddr_socket_sendto }, #endif +#if WITH_SOCKETPAIR + { "SOCKETPAIR", &xioaddr_socketpair }, +#endif #if WITH_SOCKS4 { "SOCKS", &xioaddr_socks4_connect }, { "SOCKS4", &xioaddr_socks4_connect }, diff --git a/xioopts.h b/xioopts.h index c3f1359..509809e 100644 --- a/xioopts.h +++ b/xioopts.h @@ -152,7 +152,6 @@ enum e_func { #define GROUP_FILE GROUP_REG #define GROUP_SOCKET 0x00000020 #define GROUP_READLINE 0x00000040 - #define GROUP_NAMED 0x00000100 /* file system entry */ #define GROUP_OPEN 0x00000200 /* flags for open() */ #define GROUP_EXEC 0x00000400 /* program or script execution */ diff --git a/xioread.c b/xioread.c index 36bbe0b..1399012 100644 --- a/xioread.c +++ b/xioread.c @@ -134,7 +134,27 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { #if _WITH_SOCKET case XIOREAD_RECV: - if (pipe->dtype & XIOREAD_RECV_FROM) { + if (pipe->dtype & XIOREAD_RECV_NOCHECK) { + /* No need to check peer address */ + do { + bytes = + Recv(pipe->fd, buff, bufsiz, 0); + } while (bytes < 0 && errno == EINTR); + if (bytes < 0) { + _errno = errno; + Error3("recvfrom(%d, %p, "F_Zu", 0", pipe->fd, buff, bufsiz); + errno = _errno; + return -1; + } + Notice1("received packet with "F_Zu" bytes", bytes); + if (bytes == 0) { + if (!pipe->para.socket.null_eof) { + errno = EAGAIN; return -1; + } + return bytes; + } + + } else if (pipe->dtype & XIOREAD_RECV_FROM) { /* Receiving packets in addresses of RECVFROM types, the sender address has already been determined in OPEN phase. */ Debug1("%s(): XIOREAD_RECV and XIOREAD_RECV_FROM (peer checks already done)", @@ -375,7 +395,7 @@ ssize_t xioread(xiofile_t *file, void *buff, size_t bufsiz) { return -1; #endif /* !(WITH_RAWIP || WITH_UDP || WITH_UNIX) */ - } else /* ~XIOREAD_RECV_FROM */ { + } else /* ~(XIOREAD_RECV_FROM|XIOREAD_RECV_FROM) */ { /* Receiving packets without planning to answer to the sender, but we might need sender info for some checks, thus we use recvfrom() */ struct msghdr msgh = {0}; diff --git a/xiowrite.c b/xiowrite.c index 143e1ac..769a3fd 100644 --- a/xiowrite.c +++ b/xiowrite.c @@ -114,7 +114,11 @@ ssize_t xiowrite(xiofile_t *file, const void *buff, size_t bytes) { #endif /* _WITH_SOCKET */ case XIOWRITE_PIPE: - writt = Write(pipe->para.bipipe.fdout, buff, bytes); + if (pipe->para.bipipe.socktype == SOCK_STREAM) { + writt = Write(pipe->para.bipipe.fdout, buff, bytes); + } else { + writt = Send(pipe->para.bipipe.fdout, buff, bytes, 0); + } _errno = errno; if (writt < 0) { Error4("write(%d, %p, "F_Zu"): %s",