Skip to content

Commit

Permalink
libcrun: add ring buffer implementation
Browse files Browse the repository at this point in the history
Signed-off-by: Giuseppe Scrivano <gscrivan@redhat.com>
  • Loading branch information
giuseppe committed Dec 16, 2024
1 parent 20ec098 commit e50e47c
Show file tree
Hide file tree
Showing 4 changed files with 576 additions and 2 deletions.
10 changes: 8 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ endif
check_LTLIBRARIES = libcrun_testing.la

libcrun_SOURCES = src/libcrun/utils.c \
src/libcrun/ring_buffer.c \
src/libcrun/blake3/blake3.c \
src/libcrun/blake3/blake3_portable.c \
src/libcrun/cgroup-cgroupfs.c \
Expand Down Expand Up @@ -154,12 +155,12 @@ EXTRA_DIST = COPYING COPYING.libcrun README.md NEWS SECURITY.md rpm/crun.spec au
src/libcrun/handlers/handler-utils.h \
src/libcrun/linux.h src/libcrun/utils.h src/libcrun/error.h src/libcrun/criu.h \
src/libcrun/scheduler.h src/libcrun/status.h src/libcrun/terminal.h \
src/libcrun/mount_flags.h src/libcrun/intelrdt.h \
src/libcrun/mount_flags.h src/libcrun/intelrdt.h src/libcrun/ring_buffer.h \
crun.1.md crun.1 libcrun.lds \
krun.1.md krun.1 \
lua/luacrun.rockspec

UNIT_TESTS = tests/tests_libcrun_utils tests/tests_libcrun_errors tests/tests_libcrun_intelrdt
UNIT_TESTS = tests/tests_libcrun_utils tests/tests_libcrun_ring_buffer tests/tests_libcrun_errors tests/tests_libcrun_intelrdt

if ENABLE_CRUN
bin_PROGRAMS = crun
Expand All @@ -181,6 +182,11 @@ tests_tests_libcrun_utils_SOURCES = tests/tests_libcrun_utils.c
tests_tests_libcrun_utils_LDADD = $(TESTS_LDADD)
tests_tests_libcrun_utils_LDFLAGS = $(crun_LDFLAGS)

tests_tests_libcrun_ring_buffer_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -I $(abs_top_builddir)/src -I $(abs_top_srcdir)/src
tests_tests_libcrun_ring_buffer_SOURCES = tests/tests_libcrun_ring_buffer.c
tests_tests_libcrun_ring_buffer_LDADD = $(TESTS_LDADD)
tests_tests_libcrun_ring_buffer_LDFLAGS = $(crun_LDFLAGS)

tests_tests_libcrun_intelrdt_CFLAGS = -I $(abs_top_builddir)/libocispec/src -I $(abs_top_srcdir)/libocispec/src -I $(abs_top_builddir)/src -I $(abs_top_srcdir)/src
tests_tests_libcrun_intelrdt_SOURCES = tests/tests_libcrun_intelrdt.c
tests_tests_libcrun_intelrdt_LDADD = $(TESTS_LDADD)
Expand Down
234 changes: 234 additions & 0 deletions src/libcrun/ring_buffer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,234 @@
/*
* crun - OCI runtime written in C
*
* Copyright (C) 2024 Giuseppe Scrivano <giuseppe@scrivano.org>
* crun is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* crun is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with crun. If not, see <http://www.gnu.org/licenses/>.
*/
#define _GNU_SOURCE
#include <config.h>
#include <sys/uio.h>

#include "ring_buffer.h"
#include "utils.h"

struct ring_buffer
{
char *buffer;
size_t size;
size_t head;
size_t tail;
};

/*
* It returns up to two regions in `iov` that can be read from.
*/
static int
ring_buffer_get_read_iov (struct ring_buffer *rb, struct iovec *iov)
{
int iov_count = 0;

/* Buffer is empty. */
if (rb->head == rb->tail)
return 0;

/* Head before tail. There is only one region to read from, up to tail. */
if (rb->tail > rb->head)
{
iov[iov_count].iov_base = rb->buffer + rb->head;
iov[iov_count].iov_len = rb->tail - rb->head;
iov_count++;
}
/* Head after tail. There are two regions to read from, up to the
* end of the buffer and from the beginning of the buffer to tail. */
else
{
iov[iov_count].iov_base = rb->buffer + rb->head;
iov[iov_count].iov_len = rb->size - rb->head;
iov_count++;

if (rb->tail > 0)
{
iov[iov_count].iov_base = rb->buffer;
iov[iov_count].iov_len = rb->tail;
iov_count++;
}
}
return iov_count;
}

/*
* It returns up to two regions in `iov` that can be written to without overwriting
* existing data.
*/
static int
ring_buffer_get_write_iov (struct ring_buffer *rb, struct iovec *iov)
{
int iov_count = 0;

/* Buffer is full. */
if (rb->tail + 1 == rb->head)
return 0;

/* Tail before head. There is only one region to write to, up to head. */
if (rb->head > rb->tail + 1)
{
iov[iov_count].iov_base = rb->buffer + rb->tail;
iov[iov_count].iov_len = rb->head - rb->tail - 1;
iov_count++;
}
/* Tail after or equal to head. There are two regions to write to, up to the
* end of the buffer and from the beginning of the buffer to head. */
else
{
iov[iov_count].iov_base = rb->buffer + rb->tail;
iov[iov_count].iov_len = rb->size - rb->tail;
iov_count++;

if (rb->head > 1)
{
iov[iov_count].iov_base = rb->buffer;
iov[iov_count].iov_len = rb->head - 1;
iov_count++;
}
}
return iov_count;
}

/* manually advance the head after a successful read. */
static void
ring_buffer_advance_nocheck_head (struct ring_buffer *rb, size_t amount)
{
rb->head = (rb->head + amount) % rb->size;
}

/* manually advance the tail after a successful write. */
static void
ring_buffer_advance_nocheck_tail (struct ring_buffer *rb, size_t amount)
{
rb->tail = (rb->tail + amount) % rb->size;
}

size_t
ring_buffer_get_data_available (struct ring_buffer *rb)
{
if (rb->head <= rb->tail)
return rb->tail - rb->head;

return rb->size - rb->head + rb->tail;
}

size_t
ring_buffer_get_size (struct ring_buffer *rb)
{
return rb->size - 1;
}

size_t
ring_buffer_get_space_available (struct ring_buffer *rb)
{
return rb->size - ring_buffer_get_data_available (rb) - 1;
}

int
ring_buffer_read (struct ring_buffer *rb, int fd, bool *is_eagain, libcrun_error_t *err)
{
struct iovec iov[2];
int iov_count = 0;
ssize_t ret;

*is_eagain = false;

iov_count = ring_buffer_get_write_iov (rb, iov);
if (iov_count == 0)
{
*is_eagain = true;
return 0;
}

ret = readv (fd, iov, iov_count);
if (UNLIKELY (ret < 0))
{
if (errno == EIO)
return 0;
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
*is_eagain = true;
return 0;
}
return crun_make_error (err, errno, "readv");
}
ring_buffer_advance_nocheck_tail (rb, ret);
return ret;
}

int
ring_buffer_write (struct ring_buffer *rb, int fd, bool *is_eagain, libcrun_error_t *err)
{
ssize_t ret;
struct iovec iov[2];
int iov_count = 0;

*is_eagain = false;

iov_count = ring_buffer_get_read_iov (rb, iov);
if (iov_count == 0)
{
*is_eagain = true;
return 0;
}

ret = writev (fd, iov, iov_count);
if (UNLIKELY (ret < 0))
{
if (errno == EIO)
return 0;
if (errno == EAGAIN || errno == EWOULDBLOCK)
{
*is_eagain = true;
return 0;
}
return crun_make_error (err, errno, "writev");
}
ring_buffer_advance_nocheck_head (rb, ret);
/* If the buffer is empty, reset the head and tail. */
if (rb->head == rb->tail)
{
rb->head = 0;
rb->tail = 0;
}
return ret;
}

struct ring_buffer *
ring_buffer_make (size_t size)
{
struct ring_buffer *rb = xmalloc (sizeof (struct ring_buffer));

/* The extra byte is used to distinguish between full and empty buffer. */
rb->size = size + 1;
rb->buffer = xmalloc (rb->size);
rb->head = 0;
rb->tail = 0;

return rb;
}

void
ring_buffer_free (struct ring_buffer *rb)
{
if (rb == NULL)
return;
free (rb->buffer);
free (rb);
}
52 changes: 52 additions & 0 deletions src/libcrun/ring_buffer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* crun - OCI runtime written in C
*
* Copyright (C) 2024 Giuseppe Scrivano <giuseppe@scrivano.org>
* crun is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation; either version 2.1 of the License, or
* (at your option) any later version.
*
* crun is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with crun. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef RING_BUFFER_H
#define RING_BUFFER_H

#include <config.h>

#include "error.h"
#include "utils.h"

struct ring_buffer;

size_t ring_buffer_get_data_available (struct ring_buffer *rb);

size_t ring_buffer_get_space_available (struct ring_buffer *rb);

size_t ring_buffer_get_size (struct ring_buffer *rb);

int ring_buffer_read (struct ring_buffer *rb, int fd, bool *is_eagain, libcrun_error_t *err);

int ring_buffer_write (struct ring_buffer *rb, int fd, bool *is_eagain, libcrun_error_t *err);

struct ring_buffer *ring_buffer_make (size_t size);

void ring_buffer_free (struct ring_buffer *rb);

#define cleanup_ring_buffer __attribute__ ((cleanup (cleanup_ring_bufferp)))

static inline void
cleanup_ring_bufferp (struct ring_buffer **p)
{
struct ring_buffer *rb = *p;
if (rb)
ring_buffer_free (rb);
}

#endif
Loading

0 comments on commit e50e47c

Please sign in to comment.