* Copyright (C) 2014 Red Hat, Inc.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "config.h"
#include "cockpitframe.h"
#include <assert.h>
#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_FRAME_SIZE_BYTES 8
* cockpit_frame_parse:
* @input: An buffer of bytes
* @length: The length of @input buffer
* @consumed: Number of bytes consumed from @input
*
* Parse message framing length string from the top
* of the @input buffer. These are used by Cockpit transport framing
* over a stream based protocol.
*
* Returns: The length, zero if more data is needed, or -1 if an error.
*/
ssize_t
cockpit_frame_parse (unsigned char *input,
size_t length,
size_t *consumed)
{
size_t size = 0;
size_t i;
assert (input != NULL || length == 0);
size = 0;
for (i = 0; i < length; i++)
{
if (i >= MAX_FRAME_SIZE_BYTES || (char)(input[i]) < '0' || (char)(input[i]) > '9')
break;
size *= 10;
size += (char)(input[i]) - '0';
}
if (i == length)
return 0;
* - no digits read
* - digits not followed by newline
* - size had a leading zero
*/
if (size == 0 || input[i] != '\n' || input[0] == '0')
return -1;
if (consumed)
*consumed = i + 1;
return size;
}
ssize_t
cockpit_fd_write_all (int fd,
unsigned char *data,
size_t length)
{
ssize_t written = 0;
ssize_t res;
assert (data != NULL || length == 0);
while (length > 0)
{
res = write (fd, data, length);
if (res < 0)
{
if (errno != EAGAIN && errno != EINTR)
return -1;
}
else
{
data += res;
length -= res;
written += res;
}
}
return written;
}
ssize_t
cockpit_frame_write (int fd,
unsigned char *input,
size_t length)
{
char *prefix = NULL;
ssize_t ret = -1;
int errn = 0;
assert (length > 0);
assert (input != NULL);
if (asprintf (&prefix, "%u\n", (unsigned int)length) < 0)
{
errn = ENOMEM;
goto out;
}
ret = cockpit_fd_write_all (fd, (unsigned char *)prefix, strlen (prefix));
if (ret > 0)
ret = cockpit_fd_write_all (fd, input, length);
if (ret < 0)
errn = errno;
out:
free (prefix);
if (ret < 0)
errno = errn;
return ret;
}
* @fd: a blocking file descriptor
* @buffer: where to read to
* @required_size: the exact number of bytes to read
* @was_eof: if EOF was encountered
*
* Reads exactly @required_size bytes from @fd into @buffer. If the
* correct number of bytes are read, then %TRUE is returned.
*
* On failure, returns %FALSE. If the failure was due to an underlying
* read error, then this error will be stored into errno. If the
* failure was due to an incorrect number of bytes being read, then
* errno will be set to %EBADMSG.
*
* The only permitted exception is if @was_eof is non-%NULL. In that
* case, if exactly 0 bytes are read from @fd (ie: EOF at the start of
* the message) then @was_eof will be set to %TRUE and the call will
* return successfully. Otherwise, @was_eof will be set to %FALSE on
* successful completion.
*
* If the required number of bytes couldn't be read, then any number of
* bytes may have been read from @fd. The only reasonable thing to do
* at this point is to treat the connection as broken, and close @fd.
* In particular, this is why @fd should never be non-blocking.
*/
static bool
read_exactly (int fd,
unsigned char *buffer,
size_t required_size,
bool *was_eof)
{
size_t offset = 0;
while (offset < required_size)
{
ssize_t n = read (fd, buffer + offset, required_size - offset);
if (n == -1)
{
if (errno == EINTR)
continue;
if (errno != ECONNRESET)
return false;
n = 0;
}
if (n == 0)
{
if (was_eof != NULL && offset == 0)
{
*was_eof = true;
return true;
}
errno = EBADMSG;
return false;
}
offset += n;
}
if (was_eof)
*was_eof = false;
return true;
}
ssize_t
cockpit_frame_read (int fd,
unsigned char **output)
{
* content of the frame. We want to do this efficiently as possible,
* while avoiding to read() more than the frame (since we can't put
* bytes back). MSG_PEEK is also not always available, since we're
* often talking to a pipe.
*
* Fortunately, we have a reasonable approach for this.
*
* Empty frames are invalid (cockpit_frame_parse() rejects size == 0),
* so the smallest possible frame has a length of at least 3: the
* single-digit size, the newline, then the single byte of body.
* Therefore it's always safe to read 3 bytes ("the initial read").
*
* Conveniently, reading three bytes is also always enough to tell us
* how many bytes it's safe to read in order to determine the size of
* the frame:
*
* - if we read a digit or two, followed by a newline, then we
* already know the size of the entire frame
*
* - if we read three digits, we know that the frame body is at
* least 100 bytes long. Since the maximum size of the length
* field is 8 (+1 for newline), we can safely read this entire
* amount.
*
* - if we get something other than digits or newlines, it's an
* error
*/
size_t n_read = 3;
unsigned char headerbuf[MAX_FRAME_SIZE_BYTES + 1];
bool eof;
if (!read_exactly (fd, headerbuf, n_read, &eof))
return -1;
if (eof)
{
if (output)
*output = NULL;
return 0;
}
size_t n_consumed;
ssize_t size = cockpit_frame_parse (headerbuf, n_read, &n_consumed);
if (size == 0)
{
* above, it's safe to read the rest of the buffer now (6 bytes).
* This should always result in a defined (non-zero) result.
*/
if (!read_exactly (fd, headerbuf + n_read, sizeof headerbuf - n_read, NULL))
return -1;
n_read = sizeof headerbuf;
size = cockpit_frame_parse (headerbuf, n_read, &n_consumed);
assert (size != 0);
}
if (size == -1)
{
errno = EBADMSG;
return -1;
}
* to be friendly to callers for text frames. */
unsigned char *buffer = calloc (1, size + 1);
if (buffer == NULL)
return -1;
size_t bytes_from_header = n_read - n_consumed;
memcpy (buffer, headerbuf + n_consumed, bytes_from_header);
if (!read_exactly (fd, buffer + bytes_from_header, size - bytes_from_header, NULL))
{
free (buffer);
return -1;
}
if (output)
*output = buffer;
else
free (buffer);
return size;
}