diff --git a/criu/Makefile b/criu/Makefile index bafdd980b..27997923b 100644 --- a/criu/Makefile +++ b/criu/Makefile @@ -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 diff --git a/criu/bfd.c b/criu/bfd.c index 2c5f5b64a..4daec3d14 100644 --- a/criu/bfd.c +++ b/criu/bfd.c @@ -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); diff --git a/criu/include/bfd.h b/criu/include/bfd.h index 2846ec628..050158327 100644 --- a/criu/include/bfd.h +++ b/criu/include/bfd.h @@ -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; }; diff --git a/criu/unittest/unit.c b/criu/unittest/unit.c index 54769e6f2..96d9051f9 100644 --- a/criu/unittest/unit.c +++ b/criu/unittest/unit.c @@ -1,13 +1,97 @@ #include #include #include +#include +#include +#include #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);