Skip to content

Commit 8afd6ba

Browse files
committed
bfd: Use dynamic expansion to support large groups/users
When dumping a process with a massive number of supplementary groups, the `Groups:` line in `/proc/<pid>/status` easily exceeds the hardcoded `BUFSIZE` (4096 bytes). This causes `breadchr()` to abort with "The bfd buffer is too small" Instead of globally increasing `BUFSIZE`—which unnecessarily inflates the memory footprint for all routine checkpoint operations—this patch introduces a dynamic expansion mechanism When `breadchr()` reaches the buffer limit, it dynamically maps an independent, doubled VMA to handle the long text line (capped at 2MB to prevent OOM). This oversized buffer is then safely unmapped during `buf_put()` to avoid polluting the fixed-size zero-copy batched memory pool Fixes: #2898 Signed-off-by: dong sunchao <dongsunchao@gmail.com>
1 parent cff99db commit 8afd6ba

1 file changed

Lines changed: 110 additions & 34 deletions

File tree

criu/bfd.c

Lines changed: 110 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525
*/
2626
#define BUFSIZE (PAGE_SIZE)
2727

28+
#define BFD_MAX_DYNAMIC_SIZE (2 * 1024 * 1024)
29+
2830
struct bfd_buf {
2931
char *mem;
32+
size_t size;
3033
struct list_head l;
3134
};
3235

@@ -61,6 +64,7 @@ static int buf_get(struct xbuf *xb)
6164
}
6265

6366
b->mem = mem + i * BUFSIZE;
67+
b->size = BUFSIZE;
6468
list_add_tail(&b->l, &bufs);
6569
}
6670
}
@@ -77,11 +81,20 @@ static int buf_get(struct xbuf *xb)
7781

7882
static void buf_put(struct xbuf *xb)
7983
{
80-
/*
81-
* Don't unmap buffer back, it will get reused
82-
* by next bfdopen call
83-
*/
84-
list_add(&xb->buf->l, &bufs);
84+
struct bfd_buf *b = xb->buf;
85+
86+
if (b->size > BUFSIZE) {
87+
/* This buffer was remapped to fit a long line, unmap it back */
88+
munmap(b->mem, b->size);
89+
xfree(b);
90+
} else {
91+
/*
92+
* Don't unmap standard buffer back, it will get reused
93+
* by next bfdopen call
94+
*/
95+
list_add(&b->l, &bufs);
96+
}
97+
8598
xb->buf = NULL;
8699
xb->mem = NULL;
87100
xb->data = NULL;
@@ -140,11 +153,13 @@ static int brefill(struct bfd *f)
140153
{
141154
int ret;
142155
struct xbuf *b = &f->b;
156+
struct bfd_buf *b_buf = b->buf;
157+
size_t cap = b_buf ? b_buf->size : BUFSIZE;
143158

144159
memmove(b->mem, b->data, b->sz);
145160
b->data = b->mem;
146161

147-
ret = read_all(f->fd, b->mem + b->sz, BUFSIZE - b->sz);
162+
ret = read_all(f->fd, b->mem + b->sz, cap - b->sz);
148163
if (ret < 0) {
149164
pr_perror("Error reading file");
150165
return -1;
@@ -172,12 +187,70 @@ char *breadline(struct bfd *f)
172187
return breadchr(f, '\n');
173188
}
174189

190+
static int grow_bfd_buffer(struct xbuf *b, struct bfd_buf **p_b_buf, size_t cap)
191+
{
192+
struct bfd_buf *b_buf = *p_b_buf;
193+
struct bfd_buf *new_buf;
194+
void *new_mem;
195+
size_t new_cap = cap * 2;
196+
197+
if (new_cap > BFD_MAX_DYNAMIC_SIZE) {
198+
pr_err("Line too long to fit in BFD_MAX_DYNAMIC_SIZE\n");
199+
return -EIO;
200+
}
201+
202+
new_buf = xmalloc(sizeof(*new_buf));
203+
if (!new_buf) {
204+
pr_perror("Unable to allocate buffer for bfd");
205+
return -ENOMEM;
206+
}
207+
208+
new_mem = mmap(NULL, new_cap, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, 0, 0);
209+
if (new_mem == MAP_FAILED) {
210+
pr_perror("Unable to allocate memory for bfd buffer");
211+
xfree(new_buf);
212+
return -ENOMEM;
213+
}
214+
215+
memcpy(new_mem, b->data, b->sz);
216+
217+
if (b_buf) {
218+
if (b_buf->size > BUFSIZE) {
219+
/* This buffer was remapped to fit a long line, unmap it back */
220+
munmap(b_buf->mem, b_buf->size);
221+
xfree(b_buf);
222+
} else {
223+
/*
224+
* Don't unmap standard buffer back, it will get reused
225+
* by next bfdopen call
226+
*/
227+
list_add(&b_buf->l, &bufs);
228+
}
229+
}
230+
231+
/* Update pointers with the newly allocated buffer */
232+
*p_b_buf = new_buf;
233+
b->buf = new_buf;
234+
235+
new_buf->mem = new_mem;
236+
new_buf->size = new_cap;
237+
238+
b->mem = new_mem;
239+
b->data = new_mem;
240+
241+
return 0;
242+
}
243+
175244
char *breadchr(struct bfd *f, char c)
176245
{
177246
struct xbuf *b = &f->b;
247+
struct bfd_buf *b_buf = b->buf;
178248
bool refilled = false;
179249
char *n;
250+
size_t cap;
180251
unsigned int ss = 0;
252+
int fill_ret;
253+
char *final_str;
181254

182255
again:
183256
n = strnchr(b->data + ss, b->sz - ss, c);
@@ -195,40 +268,41 @@ char *breadchr(struct bfd *f, char c)
195268
if (!b->sz)
196269
return NULL;
197270

198-
if (b->sz == BUFSIZE) {
199-
pr_err("The bfd buffer is too small\n");
200-
return ERR_PTR(-EIO);
271+
cap = b_buf ? b_buf->size : BUFSIZE;
272+
if (b->sz == cap) {
273+
int err = grow_bfd_buffer(b, &b_buf, cap);
274+
if (err)
275+
return ERR_PTR(err);
201276
}
202-
/*
203-
* Last bytes may lack the \n at the
204-
* end, need to report this as full
205-
* line anyway
206-
*/
207-
b->data[b->sz] = '\0';
208-
209-
/*
210-
* The b->data still points to old data,
211-
* but we say that no bytes left there
212-
* so next call to breadline will not
213-
* "find" these bytes again.
214-
*/
215-
b->sz = 0;
216-
return b->data;
217277
}
218278

219-
/*
220-
* small optimization -- we've scanned b->sz
221-
* symbols already, no need to re-scan them after
222-
* the buffer refill.
223-
*/
224279
ss = b->sz;
225280

226-
/* no full line in the buffer -- refill one */
227-
if (brefill(f) < 0)
281+
fill_ret = brefill(f);
282+
if (fill_ret < 0)
228283
return ERR_PTR(-EIO);
229284

230-
refilled = true;
285+
if (fill_ret == 0) {
286+
if (!b->sz)
287+
return NULL;
288+
289+
cap = b_buf ? b_buf->size : BUFSIZE;
290+
if (b->sz == cap) {
291+
int err = grow_bfd_buffer(b, &b_buf, cap);
292+
if (err)
293+
return ERR_PTR(err);
294+
}
295+
296+
final_str = b->data;
297+
final_str[b->sz] = '\0';
231298

299+
b->data += b->sz;
300+
b->sz = 0;
301+
302+
return final_str;
303+
}
304+
305+
refilled = true;
232306
goto again;
233307
}
234308

@@ -251,15 +325,17 @@ static int bflush(struct bfd *bfd)
251325
static int __bwrite(struct bfd *bfd, const void *buf, int size)
252326
{
253327
struct xbuf *b = &bfd->b;
328+
struct bfd_buf *b_buf = b->buf;
329+
size_t cap = b_buf ? b_buf->size : BUFSIZE;
254330

255-
if (b->sz + size > BUFSIZE) {
331+
if (b->sz + size > cap) {
256332
int ret;
257333
ret = bflush(bfd);
258334
if (ret < 0)
259335
return ret;
260336
}
261337

262-
if (size > BUFSIZE)
338+
if (size > cap)
263339
return write_all(bfd->fd, buf, size);
264340

265341
memcpy(b->data + b->sz, buf, size);

0 commit comments

Comments
 (0)