Skip to content

Commit 9e10887

Browse files
committed
Fix O(N^2) behavior of bind mounting.
Read /proc/self/mountinfo only once instead reading it for every "--bind" flag on the command line.
1 parent af30473 commit 9e10887

File tree

1 file changed

+165
-45
lines changed

1 file changed

+165
-45
lines changed

bind-mount.c

Lines changed: 165 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,8 @@ mount_tab_free (MountTab tab)
137137
{
138138
int i;
139139

140+
/* An allocated MountTab always ends with a zeroed MountInfo, so we can tell
141+
when to stop freeing memory. */
140142
for (i = 0; tab[i].mountpoint != NULL; i++)
141143
free (tab[i].mountpoint);
142144
free (tab);
@@ -155,15 +157,17 @@ cleanup_mount_tabp (void *p)
155157

156158
typedef struct MountInfoLine MountInfoLine;
157159
struct MountInfoLine {
158-
const char *mountpoint;
159-
const char *options;
160+
char *mountpoint;
161+
unsigned long options;
160162
bool covered;
161163
int id;
162164
int parent_id;
163165
MountInfoLine *first_child;
164166
MountInfoLine *next_sibling;
165167
};
166168

169+
typedef MountInfoLine *MountInfoLines;
170+
167171
static unsigned int
168172
count_lines (const char *data)
169173
{
@@ -211,7 +215,7 @@ collect_mounts (MountInfo *info, MountInfoLine *line)
211215
if (!line->covered)
212216
{
213217
info->mountpoint = xstrdup (line->mountpoint);
214-
info->options = decode_mountoptions (line->options);
218+
info->options = line->options;
215219
info ++;
216220
}
217221

@@ -225,21 +229,15 @@ collect_mounts (MountInfo *info, MountInfoLine *line)
225229
return info;
226230
}
227231

228-
static MountTab
229-
parse_mountinfo (int proc_fd,
230-
const char *root_mount)
232+
static MountInfoLines
233+
read_mountinfo (int proc_fd,
234+
unsigned int *mount_count)
231235
{
232236
cleanup_free char *mountinfo = NULL;
233-
cleanup_free MountInfoLine *lines = NULL;
234-
cleanup_free MountInfoLine **by_id = NULL;
235-
cleanup_mount_tab MountTab mount_tab = NULL;
236-
MountInfo *end_tab;
237-
int n_mounts;
237+
unsigned int n_lines;
238+
MountInfoLine *lines;
238239
char *line;
239240
int i;
240-
int max_id;
241-
unsigned int n_lines;
242-
int root;
243241

244242
mountinfo = load_file_at (proc_fd, "self/mountinfo");
245243
if (mountinfo == NULL)
@@ -248,10 +246,8 @@ parse_mountinfo (int proc_fd,
248246
n_lines = count_lines (mountinfo);
249247
lines = xcalloc (n_lines * sizeof (MountInfoLine));
250248

251-
max_id = 0;
252249
line = mountinfo;
253250
i = 0;
254-
root = -1;
255251
while (*line != 0)
256252
{
257253
int rc, consumed = 0;
@@ -289,30 +285,68 @@ parse_mountinfo (int proc_fd,
289285
options_end = rest;
290286

291287
*mountpoint_end = 0;
292-
lines[i].mountpoint = unescape_inline (mountpoint);
288+
lines[i].mountpoint = xstrdup (unescape_inline (mountpoint));
293289

294290
*options_end = 0;
295-
lines[i].options = options;
291+
lines[i].options = decode_mountoptions (options);
292+
293+
i++;
294+
line = next_line;
295+
}
296+
assert (i == n_lines);
297+
298+
*mount_count = n_lines;
299+
return lines;
300+
}
301+
302+
static int
303+
max_mount_id (MountInfoLines lines,
304+
unsigned int n_lines)
305+
{
306+
int i;
307+
int max_id;
296308

309+
max_id = 0;
310+
for (i = 0; i < n_lines; i++)
311+
{
297312
if (lines[i].id > max_id)
298313
max_id = lines[i].id;
299314
if (lines[i].parent_id > max_id)
300315
max_id = lines[i].parent_id;
316+
}
317+
return max_id;
318+
}
301319

320+
static MountTab
321+
parse_mountinfo (MountInfoLines lines,
322+
unsigned int n_lines,
323+
const char *root_mount)
324+
{
325+
int root;
326+
int i;
327+
int max_id;
328+
cleanup_mount_tab MountTab mount_tab = NULL;
329+
cleanup_free MountInfoLine **by_id = NULL;
330+
int n_mounts;
331+
MountInfo *end_tab;
332+
333+
root = -1;
334+
for (i = 0; i < n_lines; i++)
335+
{
302336
if (path_equal (lines[i].mountpoint, root_mount))
303337
root = i;
304-
305-
i++;
306-
line = next_line;
307338
}
308-
assert (i == n_lines);
309-
310339
if (root == -1)
311340
{
312-
mount_tab = xcalloc (sizeof (MountInfo) * (1));
341+
/* Allocate one more than required, so cleanup_mount_tabp knows when to
342+
stop freeing memory. */
343+
mount_tab = xcalloc (sizeof (MountInfo));
313344
return steal_pointer (&mount_tab);
314345
}
315346

347+
/* Allocate one more than required, so we can use IDs as indexes into
348+
by_id. */
349+
max_id = max_mount_id (lines, n_lines);
316350
by_id = xcalloc ((max_id + 1) * sizeof (MountInfoLine*));
317351
for (i = 0; i < n_lines; i++)
318352
by_id[lines[i].id] = &lines[i];
@@ -338,18 +372,18 @@ parse_mountinfo (int proc_fd,
338372
sibling = parent->first_child;
339373
while (sibling != NULL)
340374
{
341-
/* If this mountpoint is a path prefix of the sibling,
342-
* say this->mp=/foo/bar and sibling->mp=/foo, then it is
343-
* covered by the sibling, and we drop it. */
375+
/* If this mountpoint is a path prefix of the sibling, say
376+
* this->mountpoint == "/foo/bar" and sibling->mountpoiunt == "/foo",
377+
* then it is covered by the sibling, and we drop it. */
344378
if (has_path_prefix (this->mountpoint, sibling->mountpoint))
345379
{
346380
covered = TRUE;
347381
break;
348382
}
349383

350-
/* If the sibling is a path prefix of this mount point,
351-
* say this->mp=/foo and sibling->mp=/foo/bar, then the sibling
352-
* is covered, and we drop it.
384+
/* If the sibling is a path prefix of this mount point, say
385+
* this->mountpoint == "/foo" and sibling->mountpoint == "/foo/bar",
386+
* then the sibling is covered, and we drop it.
353387
*/
354388
if (has_path_prefix (sibling->mountpoint, this->mountpoint))
355389
*to_sibling = sibling->next_sibling;
@@ -365,20 +399,102 @@ parse_mountinfo (int proc_fd,
365399
}
366400

367401
n_mounts = count_mounts (&lines[root]);
368-
mount_tab = xcalloc (sizeof (MountInfo) * (n_mounts + 1));
402+
/* Allocate one more than required, so cleanup_mount_tabp knows when to stop
403+
freeing memory. */
404+
mount_tab = xcalloc ((n_mounts + 1) * sizeof (MountInfo));
369405

370406
end_tab = collect_mounts (&mount_tab[0], &lines[root]);
371407
assert (end_tab == &mount_tab[n_mounts]);
372408

373409
return steal_pointer (&mount_tab);
374410
}
375411

412+
static int
413+
find_parent (MountInfoLines lines,
414+
unsigned int line_count,
415+
char *mountpoint)
416+
{
417+
cleanup_free char *prefix = NULL;
418+
int parent_id;
419+
char *start;
420+
bool mount_found;
421+
int i;
422+
423+
prefix = xcalloc (strlen (mountpoint) + 1);
424+
prefix[0] = '/';
425+
426+
parent_id = 0;
427+
start = mountpoint;
428+
do
429+
{
430+
start = index (start, '/');
431+
if (start != NULL)
432+
{
433+
memcpy (prefix, mountpoint, start - mountpoint);
434+
start ++;
435+
}
436+
else
437+
strcpy (prefix, mountpoint);
438+
439+
do
440+
{
441+
mount_found = FALSE;
442+
for (i = 0; i < line_count; i++)
443+
{
444+
if (strcmp (lines[i].mountpoint, prefix) == 0 && lines[i].parent_id == parent_id)
445+
{
446+
parent_id = lines[i].id;
447+
mount_found = 1;
448+
break;
449+
}
450+
}
451+
}
452+
while (mount_found);
453+
}
454+
while (start != NULL);
455+
456+
return parent_id;
457+
}
458+
459+
static MountInfoLines
460+
add_mountinfo (MountInfoLines old_lines,
461+
unsigned int line_count,
462+
const char *src,
463+
char *dest)
464+
{
465+
MountInfoLines new_lines;
466+
int parent_id;
467+
int i;
468+
469+
parent_id = find_parent (old_lines, line_count, dest);
470+
471+
new_lines = xcalloc ((line_count + 1) * sizeof (MountInfoLine));
472+
for (i = 0; i < line_count; i++)
473+
{
474+
new_lines[i].mountpoint = old_lines[i].mountpoint;
475+
new_lines[i].options = old_lines[i].options;
476+
new_lines[i].id = old_lines[i].id;
477+
new_lines[i].parent_id = old_lines[i].parent_id;
478+
}
479+
new_lines[line_count].mountpoint = xstrdup (dest);
480+
new_lines[line_count].options = new_lines[parent_id].options;
481+
new_lines[line_count].id = max_mount_id (old_lines, line_count) + 1;
482+
new_lines[line_count].parent_id = parent_id;
483+
484+
free (old_lines);
485+
486+
return new_lines;
487+
}
488+
376489
int
377490
bind_mount (int proc_fd,
378491
const char *src,
379492
const char *dest,
380493
bind_option_t options)
381494
{
495+
static MountInfoLines mountinfo = NULL;
496+
static unsigned int mount_count = 0;
497+
382498
bool readonly = (options & BIND_READONLY) != 0;
383499
bool devices = (options & BIND_DEVICES) != 0;
384500
bool recursive = (options & BIND_RECURSIVE) != 0;
@@ -387,36 +503,40 @@ bind_mount (int proc_fd,
387503
cleanup_free char *resolved_dest = NULL;
388504
int i;
389505

390-
if (src)
391-
{
392-
if (mount (src, dest, NULL, MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0)
393-
return 1;
394-
}
506+
if (mountinfo == NULL)
507+
mountinfo = read_mountinfo (proc_fd, &mount_count);
395508

396-
/* The mount operation will resolve any symlinks in the destination
397-
path, so to find it in the mount table we need to do that too. */
509+
/* The mount operation will resolve any symlinks in the destination path, so
510+
we need to do that too. */
398511
resolved_dest = realpath (dest, NULL);
399512
if (resolved_dest == NULL)
400513
return 2;
401514

402-
mount_tab = parse_mountinfo (proc_fd, resolved_dest);
403-
if (mount_tab[0].mountpoint == NULL)
515+
if (src)
404516
{
405-
errno = EINVAL;
406-
return 2; /* No mountpoint at dest */
517+
if (mount (src, dest, NULL, MS_SILENT | MS_BIND | (recursive ? MS_REC : 0), NULL) != 0)
518+
return 1;
519+
520+
mountinfo = add_mountinfo (mountinfo, mount_count, src, resolved_dest);
521+
mount_count ++;
407522
}
408523

524+
mount_tab = parse_mountinfo (mountinfo, mount_count, resolved_dest);
409525
assert (path_equal (mount_tab[0].mountpoint, resolved_dest));
526+
410527
current_flags = mount_tab[0].options;
411528
new_flags = current_flags | (devices ? 0 : MS_NODEV) | MS_NOSUID | (readonly ? MS_RDONLY : 0);
412529
if (new_flags != current_flags &&
413530
mount ("none", resolved_dest,
414531
NULL, MS_SILENT | MS_BIND | MS_REMOUNT | new_flags, NULL) != 0)
415532
return 3;
416533

417-
/* We need to work around the fact that a bind mount does not apply the flags, so we need to manually
418-
* apply the flags to all submounts in the recursive case.
419-
* Note: This does not apply the flags to mounts which are later propagated into this namespace.
534+
/* We need to work around the fact that a bind mount does not apply the
535+
* flags, so we need to manually apply the flags to all submounts in the
536+
* recursive case.
537+
*
538+
* Note: This does not apply the flags to mounts that are later propagated
539+
* into this namespace.
420540
*/
421541
if (recursive)
422542
{

0 commit comments

Comments
 (0)