Compare commits
5
Commits
a83fbe3db6
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d775e09cf | ||
|
|
6a89275a06 | ||
|
|
cf23605437 | ||
|
|
5477a7c3a0 | ||
|
|
a85a646c4b |
@@ -457,6 +457,18 @@ struct hio_dev_sck_qxmsg_t
|
|||||||
};
|
};
|
||||||
typedef struct hio_dev_sck_qxmsg_t hio_dev_sck_qxmsg_t;
|
typedef struct hio_dev_sck_qxmsg_t hio_dev_sck_qxmsg_t;
|
||||||
|
|
||||||
|
/* the qx side channel is a socketpair. on most systems it is a datagram pair,
|
||||||
|
* where one write is one message and the reader is handed whole messages. where
|
||||||
|
* a datagram pair cannot carry messages reliably it is a stream pair instead,
|
||||||
|
* and the reader has to recover the message boundaries itself.
|
||||||
|
*
|
||||||
|
* the socket type, the writing side and the reader must agree on which of the
|
||||||
|
* two it is, so all three ask this one macro rather than testing for the system
|
||||||
|
* separately. */
|
||||||
|
#if defined(__BEOS__) || defined(__HAIKU__)
|
||||||
|
# define HIO_DEV_SCK_QX_STREAM
|
||||||
|
#endif
|
||||||
|
|
||||||
struct hio_dev_sck_t
|
struct hio_dev_sck_t
|
||||||
{
|
{
|
||||||
HIO_DEV_HEADER;
|
HIO_DEV_HEADER;
|
||||||
@@ -512,8 +524,16 @@ struct hio_dev_sck_t
|
|||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
hio_syshnd_t side_chan; /* side-channel for HIO_DEV_SCK_QX */
|
hio_syshnd_t side_chan; /* side-channel for HIO_DEV_SCK_QX */
|
||||||
|
#if defined(HIO_DEV_SCK_QX_STREAM)
|
||||||
|
/* the message being reassembled, and how much of it has arrived.
|
||||||
|
* a stream side channel splits and merges writes freely, so a read
|
||||||
|
* can end in the middle of a message and the remainder has to be
|
||||||
|
* held until the rest comes. only the stream variant needs it, and
|
||||||
|
* the hio_skad_t inside the message makes it large, so it is not
|
||||||
|
* carried on the systems that never look at it. */
|
||||||
hio_dev_sck_qxmsg_t qxacc;
|
hio_dev_sck_qxmsg_t qxacc;
|
||||||
hio_oow_t qxacc_len;
|
hio_oow_t qxacc_len;
|
||||||
|
#endif
|
||||||
} qx;
|
} qx;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
|
|||||||
@@ -209,7 +209,11 @@ open_socket:
|
|||||||
if (hio_makesyshndasync(hio, sck) <= -1 ||
|
if (hio_makesyshndasync(hio, sck) <= -1 ||
|
||||||
hio_makesyshndcloexec(hio, sck) <= -1) goto oops;
|
hio_makesyshndcloexec(hio, sck) <= -1) goto oops;
|
||||||
|
|
||||||
|
/* the label is only jumped to from the branch that asks socket() for the flags
|
||||||
|
* directly, so it exists only where that branch does */
|
||||||
|
#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) && !(defined(__BEOS__) || defined(__HAIKU__))
|
||||||
done:
|
done:
|
||||||
|
#endif
|
||||||
return sck;
|
return sck;
|
||||||
|
|
||||||
oops:
|
oops:
|
||||||
@@ -221,13 +225,11 @@ oops:
|
|||||||
static hio_syshnd_t open_async_qx (hio_t* hio, hio_syshnd_t* side_chan)
|
static hio_syshnd_t open_async_qx (hio_t* hio, hio_syshnd_t* side_chan)
|
||||||
{
|
{
|
||||||
int fd[2];
|
int fd[2];
|
||||||
int type = SOCK_DGRAM;
|
#if defined(HIO_DEV_SCK_QX_STREAM)
|
||||||
|
|
||||||
#if defined(__BEOS__) || defined(__HAIKU__)
|
|
||||||
/* on haiku os r6beta, SOCK_DGRAM isn't reliable. more than one write() on it causes SIGPIPE */
|
/* on haiku os r6beta, SOCK_DGRAM isn't reliable. more than one write() on it causes SIGPIPE */
|
||||||
type = SOCK_STREAM;
|
int type = SOCK_STREAM;
|
||||||
#else
|
#else
|
||||||
type = SOCK_DGRAM;
|
int type = SOCK_DGRAM;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) && !(defined(__BEOS__) || defined(__HAIKU__))
|
#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) && !(defined(__BEOS__) || defined(__HAIKU__))
|
||||||
@@ -265,7 +267,11 @@ open_socket:
|
|||||||
return HIO_SYSHND_INVALID;
|
return HIO_SYSHND_INVALID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* the label is only jumped to from the branch that asks socketpair() for the
|
||||||
|
* flags directly, so it exists only where that branch does */
|
||||||
|
#if defined(SOCK_NONBLOCK) && defined(SOCK_CLOEXEC) && !(defined(__BEOS__) || defined(__HAIKU__))
|
||||||
done:
|
done:
|
||||||
|
#endif
|
||||||
*side_chan = fd[1]; /* write end of the pipe */
|
*side_chan = fd[1]; /* write end of the pipe */
|
||||||
return fd[0]; /* read end of the pipe */
|
return fd[0]; /* read end of the pipe */
|
||||||
}
|
}
|
||||||
@@ -340,7 +346,7 @@ static int dev_sck_read_bpf (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_de
|
|||||||
{
|
{
|
||||||
hio_t* hio = dev->hio;
|
hio_t* hio = dev->hio;
|
||||||
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
|
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
|
||||||
bpf_state_t* st = (bpf_state_t*)rdev->bpf_state;
|
bpf_state_t* st = (bpf_state_t*)rdev->u.bpf.state;
|
||||||
struct bpf_hdr* bh;
|
struct bpf_hdr* bh;
|
||||||
hio_oow_t caplen;
|
hio_oow_t caplen;
|
||||||
|
|
||||||
@@ -400,7 +406,7 @@ static int dev_sck_read_bpf (hio_dev_t* dev, void* buf, hio_iolen_t* len, hio_de
|
|||||||
static int dev_sck_readpending_bpf (hio_dev_t* dev)
|
static int dev_sck_readpending_bpf (hio_dev_t* dev)
|
||||||
{
|
{
|
||||||
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
|
hio_dev_sck_t* rdev = (hio_dev_sck_t*)dev;
|
||||||
bpf_state_t* st = (bpf_state_t*)rdev->bpf_state;
|
bpf_state_t* st = (bpf_state_t*)rdev->u.bpf.state;
|
||||||
return st && st->pos < st->len;
|
return st && st->pos < st->len;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -928,9 +934,9 @@ static int dev_sck_kill (hio_dev_t* dev, int force)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_BPF)
|
#if defined(USE_BPF)
|
||||||
if (rdev->type == HIO_DEV_SCK_PACKET && rdev->u.bpf.state)
|
if (sck_type_map[rdev->type].domain == __AF_BPF && rdev->u.bpf.state)
|
||||||
{
|
{
|
||||||
bpf_state_t* st = (bpf_state_t*)rdev->bpf_state;
|
bpf_state_t* st = (bpf_state_t*)rdev->u.bpf.state;
|
||||||
if (st->buf) hio_freemem(hio, st->buf);
|
if (st->buf) hio_freemem(hio, st->buf);
|
||||||
hio_freemem(hio, st);
|
hio_freemem(hio, st);
|
||||||
rdev->u.bpf.state = HIO_NULL;
|
rdev->u.bpf.state = HIO_NULL;
|
||||||
@@ -2114,7 +2120,7 @@ static int dev_sck_ioctl (hio_dev_t* dev, int cmd, void* arg)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#if defined(USE_BPF)
|
#if defined(USE_BPF)
|
||||||
if (rdev->bpf_state)
|
if (sck_type_map[rdev->type].domain == __AF_BPF)
|
||||||
{
|
{
|
||||||
/* a bpf device attaches to an interface by name, and what the
|
/* a bpf device attaches to an interface by name, and what the
|
||||||
* caller gave is the ifindex hio_skad_init_for_eth() puts in an
|
* caller gave is the ifindex hio_skad_init_for_eth() puts in an
|
||||||
@@ -2314,6 +2320,11 @@ fcntl(rdev->hnd, F_SETFL, flags | O_NONBLOCK);
|
|||||||
/* as i know it's connected already,
|
/* as i know it's connected already,
|
||||||
* i don't schedule a connection timeout job */
|
* i don't schedule a connection timeout job */
|
||||||
|
|
||||||
|
/* the connection is up already, so there is no connect timeout
|
||||||
|
* to schedule. the field must still be marked unset: the ssl
|
||||||
|
* handshake inherits it as its deadline, and a zero there is a
|
||||||
|
* deadline already in the past. set it to a negative value */
|
||||||
|
HIO_INIT_NTIME(&rdev->tmout, -1, 0);
|
||||||
rdev->remoteaddr = conn->remoteaddr;
|
rdev->remoteaddr = conn->remoteaddr;
|
||||||
#if defined(USE_SSL)
|
#if defined(USE_SSL)
|
||||||
rdev->ssl_ctx = ssl_ctx;
|
rdev->ssl_ctx = ssl_ctx;
|
||||||
@@ -3165,9 +3176,27 @@ static int dev_evcb_sck_on_read_qx (hio_dev_t* dev, const void* data, hio_iolen_
|
|||||||
|
|
||||||
if (rdev->type == HIO_DEV_SCK_QX)
|
if (rdev->type == HIO_DEV_SCK_QX)
|
||||||
{
|
{
|
||||||
#if defined(__BEOS__) || defined(__HAIKU__)
|
#if defined(HIO_DEV_SCK_QX_STREAM)
|
||||||
const hio_uint8_t* p = (const hio_uint8_t*)data;
|
const hio_uint8_t* p;
|
||||||
hio_oow_t rem = (hio_oow_t)dlen;
|
hio_oow_t rem;
|
||||||
|
#else
|
||||||
|
hio_dev_sck_qxmsg_t* qxmsg;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (dlen <= 0)
|
||||||
|
{
|
||||||
|
/* the writing end of the side channel is gone. a qx device carries
|
||||||
|
* no stream capability bit, so the loop has nothing to recognise
|
||||||
|
* end-of-file by and would keep reading a handle that stays
|
||||||
|
* readable with nothing on it. report it as a read failure and let
|
||||||
|
* the device be halted. */
|
||||||
|
hio_seterrbfmt(hio, HIO_EINVAL, "qx side channel closed");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(HIO_DEV_SCK_QX_STREAM)
|
||||||
|
p = (const hio_uint8_t*)data;
|
||||||
|
rem = (hio_oow_t)dlen;
|
||||||
|
|
||||||
while (rem > 0)
|
while (rem > 0)
|
||||||
{
|
{
|
||||||
@@ -3179,7 +3208,6 @@ static int dev_evcb_sck_on_read_qx (hio_dev_t* dev, const void* data, hio_iolen_
|
|||||||
p += take;
|
p += take;
|
||||||
rem -= take;
|
rem -= take;
|
||||||
|
|
||||||
|
|
||||||
if (rdev->u.qx.qxacc_len < HIO_SIZEOF(rdev->u.qx.qxacc)) break; /* wait for more */
|
if (rdev->u.qx.qxacc_len < HIO_SIZEOF(rdev->u.qx.qxacc)) break; /* wait for more */
|
||||||
rdev->u.qx.qxacc_len = 0;
|
rdev->u.qx.qxacc_len = 0;
|
||||||
|
|
||||||
@@ -3188,21 +3216,24 @@ static int dev_evcb_sck_on_read_qx (hio_dev_t* dev, const void* data, hio_iolen_
|
|||||||
if (make_accepted_client_connection(rdev, rdev->u.qx.qxacc.syshnd, &rdev->u.qx.qxacc.remoteaddr, rdev->u.qx.qxacc.scktype) <= -1)
|
if (make_accepted_client_connection(rdev, rdev->u.qx.qxacc.syshnd, &rdev->u.qx.qxacc.remoteaddr, rdev->u.qx.qxacc.scktype) <= -1)
|
||||||
{
|
{
|
||||||
/*printf ("unable to accept new client connection %d\n", qxmsg->syshnd);*/
|
/*printf ("unable to accept new client connection %d\n", qxmsg->syshnd);*/
|
||||||
return (rdev->state & HIO_DEV_SCK_LENIENT)? 0: -1;
|
if (!(rdev->state & HIO_DEV_SCK_LENIENT)) return -1;
|
||||||
|
/* one read can carry several messages. the accumulator is
|
||||||
|
* empty again, so the framing is intact and the messages
|
||||||
|
* queued behind this one are still deliverable - returning
|
||||||
|
* here would throw them away along with their handles. */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
/* the stream is framed by size alone, so a bad command means
|
/* the stream is framed by size alone, so a bad command means
|
||||||
* the two ends disagree about the message layout and nothing
|
* the two ends disagree about the message layout and nothing
|
||||||
* after this point can be trusted to be a message boundary. */
|
* after this point can be trusted to be a message boundary.
|
||||||
|
* there is no resynchronising from that, so the device goes. */
|
||||||
hio_seterrbfmt(hio, HIO_EINVAL, "wrong qx command code");
|
hio_seterrbfmt(hio, HIO_EINVAL, "wrong qx command code");
|
||||||
return 0;
|
return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
hio_dev_sck_qxmsg_t* qxmsg;
|
|
||||||
|
|
||||||
if (dlen != HIO_SIZEOF(*qxmsg))
|
if (dlen != HIO_SIZEOF(*qxmsg))
|
||||||
{
|
{
|
||||||
hio_seterrbfmt(hio, HIO_EINVAL, "wrong qx packet size");
|
hio_seterrbfmt(hio, HIO_EINVAL, "wrong qx packet size");
|
||||||
@@ -3981,12 +4012,61 @@ int hio_dev_sck_sendfileok (hio_dev_sck_t* dev)
|
|||||||
|
|
||||||
int hio_dev_sck_writetosidechan (hio_dev_sck_t* dev, const void* dptr, hio_oow_t dlen)
|
int hio_dev_sck_writetosidechan (hio_dev_sck_t* dev, const void* dptr, hio_oow_t dlen)
|
||||||
{
|
{
|
||||||
if (dev->type == HIO_DEV_SCK_QX && write(dev->u.qx.side_chan, dptr, dlen) <= -1)
|
if (dev->type != HIO_DEV_SCK_QX)
|
||||||
|
{
|
||||||
|
errno = EINVAL;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(HIO_DEV_SCK_QX_STREAM)
|
||||||
|
/* the reader recovers message boundaries by counting octets, so a message
|
||||||
|
* left half-written would shift every message behind it and there is no
|
||||||
|
* recovering from that. a channel that has taken nothing yet is reported as
|
||||||
|
* a failure, the way a datagram channel reports a full socket buffer; once
|
||||||
|
* part of a message is out the channel is committed and the remainder is
|
||||||
|
* pushed out here. the reader drains this channel from the event loop, so
|
||||||
|
* the room it needs does come back. */
|
||||||
|
{
|
||||||
|
const hio_uint8_t* p = (const hio_uint8_t*)dptr;
|
||||||
|
hio_oow_t rem = dlen;
|
||||||
|
|
||||||
|
while (rem > 0)
|
||||||
|
{
|
||||||
|
ssize_t n = write(dev->u.qx.side_chan, p, rem);
|
||||||
|
if (n > 0)
|
||||||
|
{
|
||||||
|
p += n;
|
||||||
|
rem -= n;
|
||||||
|
}
|
||||||
|
else if (n == 0)
|
||||||
|
{
|
||||||
|
/* neither progress nor an error to wait on. it cannot happen on
|
||||||
|
* a socket, but looping on it would hang the caller. */
|
||||||
|
errno = EIO;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
else if (errno == EINTR ||
|
||||||
|
((errno == EAGAIN || errno == EWOULDBLOCK) && rem < dlen))
|
||||||
|
{
|
||||||
|
/* retry: interrupted, or out of room part-way through a message */
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* this doesn't set the error information on the main socket. if you may check errno, though */
|
||||||
|
/* TODO: make hio_seterrbfmt() thread safe and set the error information properly. still the caller may be in the thread-unsafe context */
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (write(dev->u.qx.side_chan, dptr, dlen) <= -1)
|
||||||
{
|
{
|
||||||
/* this doesn't set the error information on the main socket. if you may check errno, though */
|
/* this doesn't set the error information on the main socket. if you may check errno, though */
|
||||||
/* TODO: make hio_seterrbfmt() thread safe and set the error information properly. still the caller may be in the thread-unsafe context */
|
/* TODO: make hio_seterrbfmt() thread safe and set the error information properly. still the caller may be in the thread-unsafe context */
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+14
-5
@@ -646,9 +646,9 @@ int hio_sys_ctrlmux (hio_t* hio, hio_sys_mux_cmd_t cmd, hio_dev_t* dev, int dev_
|
|||||||
if (x >= 0)
|
if (x >= 0)
|
||||||
{
|
{
|
||||||
if (i_flag == EV_DISABLE && o_flag == EV_DISABLE)
|
if (i_flag == EV_DISABLE && o_flag == EV_DISABLE)
|
||||||
dev->dev_cap &= ~HIO_DEV_CAP_WATCH_SUSPENDED;
|
|
||||||
else
|
|
||||||
dev->dev_cap |= HIO_DEV_CAP_WATCH_SUSPENDED;
|
dev->dev_cap |= HIO_DEV_CAP_WATCH_SUSPENDED;
|
||||||
|
else
|
||||||
|
dev->dev_cap &= ~HIO_DEV_CAP_WATCH_SUSPENDED;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -876,6 +876,7 @@ int hio_sys_waitmux (hio_t* hio, const hio_ntime_t* tmout, hio_sys_mux_evtcb_t e
|
|||||||
for (i = 0; i < nentries; i++)
|
for (i = 0; i < nentries; i++)
|
||||||
{
|
{
|
||||||
int events = 0;
|
int events = 0;
|
||||||
|
int rdhup = 0;
|
||||||
hio_dev_t* dev;
|
hio_dev_t* dev;
|
||||||
|
|
||||||
dev = mux->revs[i].udata;
|
dev = mux->revs[i].udata;
|
||||||
@@ -887,7 +888,11 @@ int hio_sys_waitmux (hio_t* hio, const hio_ntime_t* tmout, hio_sys_mux_evtcb_t e
|
|||||||
if (mux->revs[i].ident != dev->dev_mth->getsyshnd(dev)) continue; /* already closed or something mid-loop? */
|
if (mux->revs[i].ident != dev->dev_mth->getsyshnd(dev)) continue; /* already closed or something mid-loop? */
|
||||||
|
|
||||||
if (mux->revs[i].flags & EV_ERROR) events |= HIO_DEV_EVENT_ERR;
|
if (mux->revs[i].flags & EV_ERROR) events |= HIO_DEV_EVENT_ERR;
|
||||||
if (mux->revs[i].flags & EV_EOF) events |= HIO_DEV_EVENT_HUP;
|
if (mux->revs[i].flags & EV_EOF)
|
||||||
|
{
|
||||||
|
if (mux->revs[i].filter == EVFILT_READ) rdhup = 1;
|
||||||
|
else events |= HIO_DEV_EVENT_HUP;
|
||||||
|
}
|
||||||
if (mux->revs[i].filter == EVFILT_READ) events |= HIO_DEV_EVENT_IN;
|
if (mux->revs[i].filter == EVFILT_READ) events |= HIO_DEV_EVENT_IN;
|
||||||
else if (mux->revs[i].filter == EVFILT_WRITE) events |= HIO_DEV_EVENT_OUT;
|
else if (mux->revs[i].filter == EVFILT_WRITE) events |= HIO_DEV_EVENT_OUT;
|
||||||
#else
|
#else
|
||||||
@@ -903,13 +908,17 @@ int hio_sys_waitmux (hio_t* hio, const hio_ntime_t* tmout, hio_sys_mux_evtcb_t e
|
|||||||
{
|
{
|
||||||
if (mux->revs[j].udata != dev) continue;
|
if (mux->revs[j].udata != dev) continue;
|
||||||
if (mux->revs[j].flags & EV_ERROR) events |= HIO_DEV_EVENT_ERR;
|
if (mux->revs[j].flags & EV_ERROR) events |= HIO_DEV_EVENT_ERR;
|
||||||
if (mux->revs[j].flags & EV_EOF) events |= HIO_DEV_EVENT_HUP;
|
if (mux->revs[j].flags & EV_EOF)
|
||||||
|
{
|
||||||
|
if (mux->revs[j].filter == EVFILT_READ) rdhup = 1;
|
||||||
|
else events |= HIO_DEV_EVENT_HUP;
|
||||||
|
}
|
||||||
if (mux->revs[j].filter == EVFILT_READ) events |= HIO_DEV_EVENT_IN;
|
if (mux->revs[j].filter == EVFILT_READ) events |= HIO_DEV_EVENT_IN;
|
||||||
else if (mux->revs[j].filter == EVFILT_WRITE) events |= HIO_DEV_EVENT_OUT;
|
else if (mux->revs[j].filter == EVFILT_WRITE) events |= HIO_DEV_EVENT_OUT;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (HIO_LIKELY(events)) event_handler(hio, dev, events, 0);
|
if (HIO_LIKELY(events)) event_handler(hio, dev, events, rdhup);
|
||||||
}
|
}
|
||||||
else if (mux->ctrlp[0] != HIO_SYSHND_INVALID)
|
else if (mux->ctrlp[0] != HIO_SYSHND_INVALID)
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-1
@@ -121,7 +121,8 @@ test_pxy()
|
|||||||
# and the bytes themselves, not just the count. the upstream emits a
|
# and the bytes themselves, not just the count. the upstream emits a
|
||||||
# repeating a-z pattern keyed to the offset.
|
# repeating a-z pattern keyed to the offset.
|
||||||
local sum=$(curl -s -m 60 "http://${SRVADDR}/pxy/big" | cksum | cut -d' ' -f1)
|
local sum=$(curl -s -m 60 "http://${SRVADDR}/pxy/big" | cksum | cut -d' ' -f1)
|
||||||
local want=$(perl -e 'print map { chr(97 + ($_ % 26)) } 0 .. (8*1024*1024 - 1)' 2>/dev/null | cksum | cut -d' ' -f1)
|
##local want=$(perl -e 'print map { chr(97 + ($_ % 26)) } 0 .. (8*1024*1024 - 1)' 2>/dev/null | cksum | cut -d' ' -f1)
|
||||||
|
local want=$(yes abcdefghijklmnopqrstuvwxyz | tr -d '\n' | head -c 8388608 | cksum | cut -d' ' -f1)
|
||||||
if [ -n "$want" ]; then
|
if [ -n "$want" ]; then
|
||||||
tap_ensure "$sum" "$want" "$msg - the relayed bytes are identical to the upstream's"
|
tap_ensure "$sum" "$want" "$msg - the relayed bytes are identical to the upstream's"
|
||||||
else
|
else
|
||||||
|
|||||||
Reference in New Issue
Block a user