bfd: Support dynamic buffer resizing

Currently, bfd has a fixed buffer size (BUFSIZE, which is 4096). This
causes issues when reading lines longer than BUFSIZE, as breadline
fails with "The bfd buffer is too small".

This patch introduces dynamic buffer resizing in bfd. When a buffer
is full and more space is needed (e.g., for a very long line), the
buffer is resized using mremap (or a new mmap if it was using a
pre-allocated buffer from the pool).

A new bsize field is added to struct xbuf to keep track of the
current buffer size.

Unit tests for reading long lines and writing large buffers are
added to criu/unittest/unit.c.

Signed-off-by: dong sunchao <dongsunchao@gmail.com>
Co-developed-by: Andrei Vagin <avagin@google.com>
Signed-off-by: Andrei Vagin <avagin@google.com>
This commit is contained in:
dong sunchao 2026-03-31 19:21:17 +00:00 committed by Alexander Mikhalitsyn
parent 2c351c73d7
commit b99cbc1e8c
No known key found for this signature in database
GPG key ID: B1F47F5CB05B4FA3
4 changed files with 146 additions and 11 deletions

View file

@ -88,6 +88,7 @@ $(obj)/criu: $(PROGRAM-BUILTINS)
$(Q) $(CC) $(CFLAGS) $^ $(LDFLAGS) $(LIBS) $(WRAPFLAGS) $(GMONLDOPT) -rdynamic -o $@
UNIT-BUILTINS += $(obj)/util.o
UNIT-BUILTINS += $(obj)/bfd.o
UNIT-BUILTINS += $(obj)/config.o
UNIT-BUILTINS += $(obj)/log.o
UNIT-BUILTINS += $(obj)/string.o

View file

@ -25,6 +25,8 @@
*/
#define BUFSIZE (PAGE_SIZE)
#define BFD_MAX_DYNAMIC_SIZE (2 * 1024 * 1024)
struct bfd_buf {
char *mem;
struct list_head l;
@ -70,6 +72,7 @@ static int buf_get(struct xbuf *xb)
xb->mem = b->mem;
xb->data = xb->mem;
xb->bsize = BUFSIZE;
xb->sz = 0;
xb->buf = b;
return 0;
@ -77,11 +80,16 @@ static int buf_get(struct xbuf *xb)
static void buf_put(struct xbuf *xb)
{
/*
* Don't unmap buffer back, it will get reused
* by next bfdopen call
*/
list_add(&xb->buf->l, &bufs);
if (xb->buf) {
/*
* Don't unmap standard buffer back, it will get reused
* by next bfdopen call
*/
list_add(&xb->buf->l, &bufs);
} else {
/* This buffer was dynamically extended, unmap it */
munmap(xb->mem, xb->bsize);
}
xb->buf = NULL;
xb->mem = NULL;
xb->data = NULL;
@ -144,7 +152,7 @@ static int brefill(struct bfd *f)
memmove(b->mem, b->data, b->sz);
b->data = b->mem;
ret = read_all(f->fd, b->mem + b->sz, BUFSIZE - b->sz);
ret = read_all(f->fd, b->mem + b->sz, b->bsize - b->sz);
if (ret < 0) {
pr_perror("Error reading file");
return -1;
@ -172,6 +180,40 @@ char *breadline(struct bfd *f)
return breadchr(f, '\n');
}
static int bextend(struct bfd *f)
{
struct xbuf *b = &f->b;
void *newbuf;
long newsize = b->bsize * 2;
if (newsize > BFD_MAX_DYNAMIC_SIZE) {
pr_err("Line too long to fit in BFD_MAX_DYNAMIC_SIZE\n");
return -1;
}
if (b->buf) {
newbuf = mmap(NULL, newsize, PROT_READ | PROT_WRITE,
MAP_ANONYMOUS | MAP_PRIVATE, 0, 0);
if (newbuf == MAP_FAILED) {
pr_perror("Error allocating buffer");
return -1;
}
memcpy(newbuf, b->mem, b->sz);
list_add(&b->buf->l, &bufs);
b->buf = NULL;
} else {
newbuf = mremap(b->mem, b->bsize, newsize, MREMAP_MAYMOVE);
if (newbuf == MAP_FAILED) {
pr_perror("Error allocating buffer");
return -1;
}
}
b->mem = newbuf;
b->data = newbuf;
b->bsize = newsize;
return 0;
}
char *breadchr(struct bfd *f, char c)
{
struct xbuf *b = &f->b;
@ -195,9 +237,12 @@ again:
if (!b->sz)
return NULL;
if (b->sz == BUFSIZE) {
pr_err("The bfd buffer is too small\n");
return ERR_PTR(-EIO);
if (b->sz == b->bsize) {
if (bextend(f)) {
pr_err("The bfd buffer is too small\n");
return ERR_PTR(-EIO);
}
goto refill;
}
/*
* Last bytes may lack the \n at the
@ -216,6 +261,7 @@ again:
return b->data;
}
refill:
/*
* small optimization -- we've scanned b->sz
* symbols already, no need to re-scan them after
@ -252,14 +298,14 @@ static int __bwrite(struct bfd *bfd, const void *buf, int size)
{
struct xbuf *b = &bfd->b;
if (b->sz + size > BUFSIZE) {
if (b->sz + size > b->bsize) {
int ret;
ret = bflush(bfd);
if (ret < 0)
return ret;
}
if (size > BUFSIZE)
if (size > b->bsize)
return write_all(bfd->fd, buf, size);
memcpy(b->data + b->sz, buf, size);

View file

@ -8,6 +8,7 @@ struct xbuf {
char *mem; /* buffer */
char *data; /* position we see bytes at */
unsigned int sz; /* bytes sitting after b->pos */
unsigned int bsize;
struct bfd_buf *buf;
};

View file

@ -1,13 +1,97 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include "log.h"
#include "util.h"
#include "criu-log.h"
#include "bfd.h"
int parse_statement(int i, char *line, char **configuration);
static void test_bfd(void)
{
struct bfd f;
char *str;
const int lines = 5;
char *long_line[lines];
int size = 1024 * 1024;
int i, fd;
fd = memfd_create("criu-bfd-test", 0);
assert(fd >= 0);
for (i = 0; i < lines; i++) {
int j;
long_line[i] = malloc(size + 2);
assert(long_line[i]);
long_line[i][0] = 'A' + (i % 26);
for (j = 1; j < size; j++)
long_line[i][j] = 'a' + (j % 26);
long_line[i][size] = '\n';
long_line[i][size + 1] = '\0';
assert(write(fd, long_line[i], size + 1) == size + 1);
}
assert(lseek(fd, 0, SEEK_SET) == 0);
f.fd = fd;
assert(bfdopenr(&f) == 0);
for (i = 0; i < lines; i++) {
str = breadline(&f);
assert(str);
assert(strlen(str) == size);
/* long_line has \n, str hasn't */
assert(strcmp(str, long_line[i]) != 0);
str[size] = '\n';
assert(memcmp(str, long_line[i], size + 1) == 0);
}
bclose(&f);
for (i = 0; i < lines; i++)
free(long_line[i]);
}
static void test_bwrite(void)
{
struct bfd f;
char *buf;
int size = 1024 * 1024;
int i;
int fd;
char *read_buf;
fd = memfd_create("criu-bfd-test", 0);
assert(fd >= 0);
buf = malloc(size);
assert(buf);
for (i = 0; i < size; i++)
buf[i] = 'z' - (i % 26);
f.fd = dup(fd);
assert(f.fd >= 0);
assert(bfdopenw(&f) == 0);
assert(bwrite(&f, buf, size) == size);
bclose(&f);
assert(lseek(fd, 0, SEEK_SET) == 0);
read_buf = malloc(size);
assert(read_buf);
assert(read(fd, read_buf, size) == size);
assert(memcmp(buf, read_buf, size) == 0);
close(fd);
free(buf);
free(read_buf);
}
int main(int argc, char *argv[], char *envp[])
{
char **configuration;
@ -16,6 +100,9 @@ int main(int argc, char *argv[], char *envp[])
configuration = malloc(10 * sizeof(char *));
log_init(NULL);
test_bfd();
test_bwrite();
i = parse_statement(0, "", configuration);
assert(i == 0);