This answer by Tom Hale goes on to clarify:
...it appears that the filesystem has been unmounted, but in reality it has only been hidden from the file namespace / heirarchy.
- Processes can still write via open file descriptors
- New or existing files can be opened for writing by processes with a working directory inside the mountpoint via relative pathnames
Tom's answer really hit the nail on the head, but to reiterate:
"Unavailable for new access" simply means you cannot resolve path names that include the mountpoint.
You can do anything with the mountpoint, except open new files/directories by absolute path.
The only thing you can be certain that happens after calling umount(MNT_DETACH)
is that the below the mountpoint are inaccessible by name.
The option name MNT_DETACH
even explains this behavior: The mountpoint is detached from the directory hierarchy, but nothing about the actual mounted filesystem is guaranteed to have happened.
It's somewhat obvious when you think about it, but the current working directory is essentially an open file reference to that directory, but maintained by the kernel. Thus:
chdir("/foo");
open("./bar.txt", O_RDONLY);
is equivalent to
chdir("/foo");
openat(AT_FDCWD, "bar.txt", O_RDONLY);
which is equivalent to
int dirfd = open("/foo", O_RDONLY | O_DIRECTORY);
openat(dirfd, "bar.txt", O_RDONLY);
I did some tests regarding lazy unbinding and open directories:
- If you have an open file descriptor referencing a directory on the mountpoint:
- You can still call
getdents(2)
to read the directory contents
- You can still use
openat(2)
to open files underneath that directory, using their path relative to it!
This program demonstrates:
#define _GNU_SOURCE
#include <dirent.h>
#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/mount.h>
static void
show_dir_listing(DIR *dir)
{
printf("Listing directory (by handle):\n");
rewinddir(dir);
for (;;) {
struct dirent *d;
errno = 0;
d = readdir(dir);
if (d == NULL) {
if (errno)
error(2, errno, "readdir failed");
break;
}
printf(" %s%s\n",
d->d_name,
(d->d_type == DT_DIR) ? "/" : "");
}
}
int main(int argc, char **argv)
{
const char *dirpath;
const char *filename;
DIR *dir;
int fd, rc;
if (argc < 3) {
fprintf(stderr, "Usage: %s DIR FILE\n",
program_invocation_short_name);
return 1;
}
dirpath = argv[1];
filename = argv[2];
printf("PID: %u\n", (unsigned int)getpid());
printf("Opening handle to %s\n", dirpath);
dir = opendir(dirpath);
if (dir == NULL)
error(2, errno, "opendir failed: %s", dirpath);
show_dir_listing(dir);
printf("\nLazy-unmounting %s\n\n", dirpath);
rc = umount2(dirpath, MNT_DETACH);
if (rc < 0)
error(2, errno, "umount2 failed");
show_dir_listing(dir);
/* Try to open by full path name */
{
char path[PATH_MAX];
path[0] = '\0';
strcat(path, dirpath);
strcat(path, "/");
strcat(path, filename);
printf("Trying to open(\"%s\")... ", path);
fd = open(path, O_RDONLY);
if (fd < 0) {
printf("Failed!\n");
}
else {
printf("Success: fd=%d\n", fd);
close(fd);
}
}
/* Try to openat relative to dir */
{
int dfd = dirfd(dir);
printf("Trying to openat(%d, \"%s\")... ", dfd, filename);
fd = openat(dfd, filename, O_RDONLY);
if (fd < 0) {
printf("Failed!\n");
}
else {
printf("Success: fd=%d\n", fd);
close(fd);
}
}
return 0;
}
Testing:
$ ls /tmp/to-be-bound/
bar.txt crackle.txt foo.txt pop.txt snap.txt
$ mkdir /tmp/readonly-bind
$ mount -o bind,ro /tmp/to-be-bound /tmp/readonly-bind
$ ls /tmp/readonly-bind/
bar.txt crackle.txt foo.txt pop.txt snap.txt
$ echo 'should fail' >> /tmp/readonly-bind/foo.txt
-bash: /tmp/readonly-bind/foo.txt: Read-only file system
$ sudo ./lazytest /tmp/readonly-bind foo.txt
PID: 21160
Opening handle to /tmp/readonly-bind
Listing directory (by handle):
./
../
pop.txt
crackle.txt
snap.txt
bar.txt
foo.txt
Lazy-unmounting /tmp/readonly-bind
Listing directory (by handle):
./
../
pop.txt
crackle.txt
snap.txt
bar.txt
foo.txt
Trying to open("/tmp/readonly-bind/foo.txt")... Failed!
Trying to openat(3, "foo.txt")... Success: fd=4