4

umount --lazy calls umount(2) with the MNT_DETACH flag set. umount(2) says that will:

"Perform a lazy unmount: make the mount point unavailable for new accesses, immediately disconnect the filesystem and all filesystems mounted below it from each other and from the mount table, and actually perform the unmount when the mount point ceases to be busy.

umount(8) says that a file system is busy...

for example, when there are open files on it, or when some process has its working directory there, or when a swap file on it is in use.

But what exactly does "unavailable for new access" mean? I've seen applications that chdir(2) into a directory which is subsequently unmounted, and they behave just fine.

Tom Hale
  • 30,455

1 Answers1

3

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